├── .github ├── FUNDING.yml ├── workflows │ ├── release.yml │ └── ci.yml ├── ISSUE_TEMPLATE.md └── CONTRIBUTING.md ├── .prettierrc.json ├── resources ├── react-global.js ├── identity.ai ├── jasmine-check.d.ts ├── copyright.mjs ├── check-git-clean.sh ├── jestResolver.js ├── rollup-config-es.mjs ├── prepare-dist.sh ├── rollup-config.mjs ├── jestPreprocessor.js └── dist-stats.js ├── src ├── utils │ ├── hasOwnProperty.js │ ├── invariant.js │ ├── createClass.js │ ├── assertNotInfinite.js │ ├── arrCopy.js │ ├── quoteString.js │ ├── mixin.js │ ├── shallowCopy.js │ ├── coerceKeyPath.js │ ├── isDataStructure.js │ ├── isArrayLike.js │ ├── isPlainObj.js │ └── deepEqual.js ├── methods │ ├── wasAltered.js │ ├── asImmutable.js │ ├── deleteIn.js │ ├── setIn.js │ ├── hasIn.js │ ├── asMutable.js │ ├── getIn.js │ ├── withMutations.js │ ├── updateIn.js │ ├── update.js │ ├── toObject.js │ ├── mergeDeep.js │ ├── mergeIn.js │ ├── README.md │ ├── mergeDeepIn.js │ └── merge.js ├── PairSorting.js ├── Immutable.js ├── predicates │ ├── isMap.js │ ├── isSeq.js │ ├── isSet.js │ ├── isList.js │ ├── isKeyed.js │ ├── isStack.js │ ├── isRecord.js │ ├── isIndexed.js │ ├── isOrdered.js │ ├── isOrderedMap.js │ ├── isOrderedSet.js │ ├── isValueObject.js │ ├── isAssociative.js │ ├── isImmutable.js │ └── isCollection.js ├── functional │ ├── update.js │ ├── hasIn.js │ ├── removeIn.js │ ├── setIn.js │ ├── has.js │ ├── get.js │ ├── getIn.js │ ├── set.js │ ├── remove.js │ ├── updateIn.js │ └── merge.js ├── toJS.js ├── Math.js ├── fromJS.js ├── OrderedSet.js ├── Iterator.js ├── Repeat.js ├── TrieUtils.js ├── is.js └── Range.js ├── website ├── .eslintrc ├── next.config.js ├── public │ ├── favicon.png │ └── Immutable-Data-and-React-YouTube.png ├── src │ ├── static │ │ ├── .eslintrc.json │ │ ├── genMarkdownDoc.ts │ │ ├── stripUndefineds.ts │ │ └── getVersions.js │ ├── SVGSet.tsx │ ├── isMobile.ts │ ├── pages │ │ ├── _app.tsx │ │ ├── index.tsx │ │ └── docs │ │ │ ├── index.tsx │ │ │ └── [version] │ │ │ ├── [type].tsx │ │ │ └── index.tsx │ ├── DocHeader.tsx │ ├── MarkdownContent.tsx │ ├── collectMemberGroups.ts │ ├── ArrowDown.tsx │ ├── DocSearch.tsx │ ├── DocOverview.tsx │ ├── ImmutableConsole.tsx │ ├── MemberDoc.tsx │ ├── TypeDefs.ts │ └── RunkitEmbed.tsx ├── next-env.d.ts ├── next-sitemap.js └── tsconfig.json ├── .eslintignore ├── .prettierignore ├── type-definitions ├── ts-tests │ ├── index.d.ts │ ├── range.ts │ ├── tslint.json │ ├── repeat.ts │ ├── seq.ts │ ├── tsconfig.json │ ├── es6-collections.ts │ ├── from-js.ts │ ├── empty.ts │ ├── groupBy.ts │ ├── functional.ts │ ├── covariance.ts │ ├── record.ts │ ├── exports.ts │ └── deepCopy.ts ├── flow-tests │ ├── .flowconfig │ ├── predicates.js │ ├── es6-collections.js │ └── covariance.js └── tsconfig.json ├── __tests__ ├── .eslintrc.json ├── tsconfig.json ├── Repeat.ts ├── __snapshots__ │ └── Record.ts.snap ├── find.ts ├── interpose.ts ├── join.ts ├── get.ts ├── IndexedSeq.ts ├── ListJS.js ├── Predicates.ts ├── hash.ts ├── ObjectSeq.ts ├── sort.ts ├── RecordJS.js ├── utils.js ├── splice.ts ├── fromJS.ts ├── Comparator.ts ├── hasIn.ts ├── count.ts ├── MultiRequire.js ├── partition.ts ├── ArraySeq.ts ├── groupBy.ts ├── KeyedSeq.ts ├── minmax.ts ├── transformerProtocol.ts ├── flatten.ts ├── getIn.ts ├── zip.ts └── OrderedMap.ts ├── .editorconfig ├── .gitignore ├── README.md ├── perf ├── toJS.js ├── Record.js ├── List.js └── Map.js ├── jest.config.js ├── package-publish.json ├── tslint.json ├── LICENSE └── .eslintrc.json /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: [Methuselah96] 2 | -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true 3 | } 4 | -------------------------------------------------------------------------------- /resources/react-global.js: -------------------------------------------------------------------------------- 1 | module.exports = global.React; 2 | -------------------------------------------------------------------------------- /src/utils/hasOwnProperty.js: -------------------------------------------------------------------------------- 1 | export default Object.prototype.hasOwnProperty; 2 | -------------------------------------------------------------------------------- /website/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["next", "next/core-web-vitals"] 3 | } 4 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | /pages/out/** 2 | /pages/generated/** 3 | /dist/** 4 | node_modules 5 | -------------------------------------------------------------------------------- /src/methods/wasAltered.js: -------------------------------------------------------------------------------- 1 | export function wasAltered() { 2 | return this.__altered; 3 | } 4 | -------------------------------------------------------------------------------- /resources/identity.ai: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nkzw-tech/immutable-map/HEAD/resources/identity.ai -------------------------------------------------------------------------------- /src/methods/asImmutable.js: -------------------------------------------------------------------------------- 1 | export function asImmutable() { 2 | return this.__ensureOwner(); 3 | } 4 | -------------------------------------------------------------------------------- /src/PairSorting.js: -------------------------------------------------------------------------------- 1 | export const PairSorting = { 2 | LeftThenRight: -1, 3 | RightThenLeft: +1, 4 | }; 5 | -------------------------------------------------------------------------------- /website/next.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | reactStrictMode: true, 3 | trailingSlash: true, 4 | }; 5 | -------------------------------------------------------------------------------- /website/public/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nkzw-tech/immutable-map/HEAD/website/public/favicon.png -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | dist 2 | pages/generated 3 | pages/out 4 | type-definitions/ts-tests/ 5 | type-definitions/flow-tests/ -------------------------------------------------------------------------------- /type-definitions/ts-tests/index.d.ts: -------------------------------------------------------------------------------- 1 | // Minimum TypeScript Version: 4.5 2 | /* tslint:disable:no-useless-files */ 3 | -------------------------------------------------------------------------------- /src/Immutable.js: -------------------------------------------------------------------------------- 1 | import { Map } from './Map'; 2 | import { Collection } from './CollectionImpl'; 3 | 4 | export default Map; 5 | -------------------------------------------------------------------------------- /src/utils/invariant.js: -------------------------------------------------------------------------------- 1 | export default function invariant(condition, error) { 2 | if (!condition) throw new Error(error); 3 | } 4 | -------------------------------------------------------------------------------- /website/src/static/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "node": true 4 | }, 5 | "extends": "../../../.eslintrc.json" 6 | } 7 | -------------------------------------------------------------------------------- /__tests__/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "jest": true 4 | }, 5 | "parserOptions": { 6 | "ecmaVersion": 6 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /resources/jasmine-check.d.ts: -------------------------------------------------------------------------------- 1 | // TODO: proper types 2 | declare var check: any; 3 | declare var gen: any; 4 | declare module 'jasmine-check'; 5 | -------------------------------------------------------------------------------- /website/next-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | /// 4 | -------------------------------------------------------------------------------- /website/public/Immutable-Data-and-React-YouTube.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nkzw-tech/immutable-map/HEAD/website/public/Immutable-Data-and-React-YouTube.png -------------------------------------------------------------------------------- /src/methods/deleteIn.js: -------------------------------------------------------------------------------- 1 | import { removeIn } from '../functional/removeIn'; 2 | 3 | export function deleteIn(keyPath) { 4 | return removeIn(this, keyPath); 5 | } 6 | -------------------------------------------------------------------------------- /src/methods/setIn.js: -------------------------------------------------------------------------------- 1 | import { setIn as _setIn } from '../functional/setIn'; 2 | 3 | export function setIn(keyPath, v) { 4 | return _setIn(this, keyPath, v); 5 | } 6 | -------------------------------------------------------------------------------- /src/methods/hasIn.js: -------------------------------------------------------------------------------- 1 | import { hasIn as _hasIn } from '../functional/hasIn'; 2 | 3 | export function hasIn(searchKeyPath) { 4 | return _hasIn(this, searchKeyPath); 5 | } 6 | -------------------------------------------------------------------------------- /src/methods/asMutable.js: -------------------------------------------------------------------------------- 1 | import { OwnerID } from '../TrieUtils'; 2 | 3 | export function asMutable() { 4 | return this.__ownerID ? this : this.__ensureOwner(new OwnerID()); 5 | } 6 | -------------------------------------------------------------------------------- /src/predicates/isMap.js: -------------------------------------------------------------------------------- 1 | export const IS_MAP_SYMBOL = '@@__IMMUTABLE_MAP__@@'; 2 | 3 | export function isMap(maybeMap) { 4 | return Boolean(maybeMap && maybeMap[IS_MAP_SYMBOL]); 5 | } 6 | -------------------------------------------------------------------------------- /src/predicates/isSeq.js: -------------------------------------------------------------------------------- 1 | export const IS_SEQ_SYMBOL = '@@__IMMUTABLE_SEQ__@@'; 2 | 3 | export function isSeq(maybeSeq) { 4 | return Boolean(maybeSeq && maybeSeq[IS_SEQ_SYMBOL]); 5 | } 6 | -------------------------------------------------------------------------------- /src/predicates/isSet.js: -------------------------------------------------------------------------------- 1 | export const IS_SET_SYMBOL = '@@__IMMUTABLE_SET__@@'; 2 | 3 | export function isSet(maybeSet) { 4 | return Boolean(maybeSet && maybeSet[IS_SET_SYMBOL]); 5 | } 6 | -------------------------------------------------------------------------------- /src/predicates/isList.js: -------------------------------------------------------------------------------- 1 | export const IS_LIST_SYMBOL = '@@__IMMUTABLE_LIST__@@'; 2 | 3 | export function isList(maybeList) { 4 | return Boolean(maybeList && maybeList[IS_LIST_SYMBOL]); 5 | } 6 | -------------------------------------------------------------------------------- /src/methods/getIn.js: -------------------------------------------------------------------------------- 1 | import { getIn as _getIn } from '../functional/getIn'; 2 | 3 | export function getIn(searchKeyPath, notSetValue) { 4 | return _getIn(this, searchKeyPath, notSetValue); 5 | } 6 | -------------------------------------------------------------------------------- /src/predicates/isKeyed.js: -------------------------------------------------------------------------------- 1 | export const IS_KEYED_SYMBOL = '@@__IMMUTABLE_KEYED__@@'; 2 | 3 | export function isKeyed(maybeKeyed) { 4 | return Boolean(maybeKeyed && maybeKeyed[IS_KEYED_SYMBOL]); 5 | } 6 | -------------------------------------------------------------------------------- /src/predicates/isStack.js: -------------------------------------------------------------------------------- 1 | export const IS_STACK_SYMBOL = '@@__IMMUTABLE_STACK__@@'; 2 | 3 | export function isStack(maybeStack) { 4 | return Boolean(maybeStack && maybeStack[IS_STACK_SYMBOL]); 5 | } 6 | -------------------------------------------------------------------------------- /src/functional/update.js: -------------------------------------------------------------------------------- 1 | import { updateIn } from './updateIn'; 2 | 3 | export function update(collection, key, notSetValue, updater) { 4 | return updateIn(collection, [key], notSetValue, updater); 5 | } 6 | -------------------------------------------------------------------------------- /src/predicates/isRecord.js: -------------------------------------------------------------------------------- 1 | export const IS_RECORD_SYMBOL = '@@__IMMUTABLE_RECORD__@@'; 2 | 3 | export function isRecord(maybeRecord) { 4 | return Boolean(maybeRecord && maybeRecord[IS_RECORD_SYMBOL]); 5 | } 6 | -------------------------------------------------------------------------------- /src/methods/withMutations.js: -------------------------------------------------------------------------------- 1 | export function withMutations(fn) { 2 | const mutable = this.asMutable(); 3 | fn(mutable); 4 | return mutable.wasAltered() ? mutable.__ensureOwner(this.__ownerID) : this; 5 | } 6 | -------------------------------------------------------------------------------- /src/predicates/isIndexed.js: -------------------------------------------------------------------------------- 1 | export const IS_INDEXED_SYMBOL = '@@__IMMUTABLE_INDEXED__@@'; 2 | 3 | export function isIndexed(maybeIndexed) { 4 | return Boolean(maybeIndexed && maybeIndexed[IS_INDEXED_SYMBOL]); 5 | } 6 | -------------------------------------------------------------------------------- /src/predicates/isOrdered.js: -------------------------------------------------------------------------------- 1 | export const IS_ORDERED_SYMBOL = '@@__IMMUTABLE_ORDERED__@@'; 2 | 3 | export function isOrdered(maybeOrdered) { 4 | return Boolean(maybeOrdered && maybeOrdered[IS_ORDERED_SYMBOL]); 5 | } 6 | -------------------------------------------------------------------------------- /src/functional/hasIn.js: -------------------------------------------------------------------------------- 1 | import { getIn } from './getIn'; 2 | import { NOT_SET } from '../TrieUtils'; 3 | 4 | export function hasIn(collection, keyPath) { 5 | return getIn(collection, keyPath, NOT_SET) !== NOT_SET; 6 | } 7 | -------------------------------------------------------------------------------- /src/methods/updateIn.js: -------------------------------------------------------------------------------- 1 | import { updateIn as _updateIn } from '../functional/updateIn'; 2 | 3 | export function updateIn(keyPath, notSetValue, updater) { 4 | return _updateIn(this, keyPath, notSetValue, updater); 5 | } 6 | -------------------------------------------------------------------------------- /src/utils/createClass.js: -------------------------------------------------------------------------------- 1 | export default function createClass(ctor, superClass) { 2 | if (superClass) { 3 | ctor.prototype = Object.create(superClass.prototype); 4 | } 5 | ctor.prototype.constructor = ctor; 6 | } 7 | -------------------------------------------------------------------------------- /src/functional/removeIn.js: -------------------------------------------------------------------------------- 1 | import { updateIn } from './updateIn'; 2 | import { NOT_SET } from '../TrieUtils'; 3 | 4 | export function removeIn(collection, keyPath) { 5 | return updateIn(collection, keyPath, () => NOT_SET); 6 | } 7 | -------------------------------------------------------------------------------- /src/functional/setIn.js: -------------------------------------------------------------------------------- 1 | import { updateIn } from './updateIn'; 2 | import { NOT_SET } from '../TrieUtils'; 3 | 4 | export function setIn(collection, keyPath, value) { 5 | return updateIn(collection, keyPath, NOT_SET, () => value); 6 | } 7 | -------------------------------------------------------------------------------- /src/predicates/isOrderedMap.js: -------------------------------------------------------------------------------- 1 | import { isMap } from './isMap'; 2 | import { isOrdered } from './isOrdered'; 3 | 4 | export function isOrderedMap(maybeOrderedMap) { 5 | return isMap(maybeOrderedMap) && isOrdered(maybeOrderedMap); 6 | } 7 | -------------------------------------------------------------------------------- /src/predicates/isOrderedSet.js: -------------------------------------------------------------------------------- 1 | import { isSet } from './isSet'; 2 | import { isOrdered } from './isOrdered'; 3 | 4 | export function isOrderedSet(maybeOrderedSet) { 5 | return isSet(maybeOrderedSet) && isOrdered(maybeOrderedSet); 6 | } 7 | -------------------------------------------------------------------------------- /src/predicates/isValueObject.js: -------------------------------------------------------------------------------- 1 | export function isValueObject(maybeValue) { 2 | return Boolean( 3 | maybeValue && 4 | typeof maybeValue.equals === 'function' && 5 | typeof maybeValue.hashCode === 'function' 6 | ); 7 | } 8 | -------------------------------------------------------------------------------- /type-definitions/ts-tests/range.ts: -------------------------------------------------------------------------------- 1 | import { Range } from 'immutable'; 2 | 3 | { 4 | // #constructor 5 | 6 | // $ExpectType Indexed 7 | Range(0, 0, 0); 8 | 9 | // $ExpectError 10 | Range('a', 0, 0); 11 | } 12 | -------------------------------------------------------------------------------- /resources/copyright.mjs: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | 3 | const copyright = fs.readFileSync('./LICENSE', 'utf-8'); 4 | const lines = copyright.trim().split('\n'); 5 | 6 | export default `/**\n${lines.map(line => ` * ${line}`).join('\n')}\n */`; 7 | -------------------------------------------------------------------------------- /src/predicates/isAssociative.js: -------------------------------------------------------------------------------- 1 | import { isKeyed } from './isKeyed'; 2 | import { isIndexed } from './isIndexed'; 3 | 4 | export function isAssociative(maybeAssociative) { 5 | return isKeyed(maybeAssociative) || isIndexed(maybeAssociative); 6 | } 7 | -------------------------------------------------------------------------------- /src/utils/assertNotInfinite.js: -------------------------------------------------------------------------------- 1 | import invariant from './invariant'; 2 | 3 | export default function assertNotInfinite(size) { 4 | invariant( 5 | size !== Infinity, 6 | 'Cannot perform this action with an infinite size.' 7 | ); 8 | } 9 | -------------------------------------------------------------------------------- /src/methods/update.js: -------------------------------------------------------------------------------- 1 | import { update as _update } from '../functional/update'; 2 | 3 | export function update(key, notSetValue, updater) { 4 | return arguments.length === 1 5 | ? key(this) 6 | : _update(this, key, notSetValue, updater); 7 | } 8 | -------------------------------------------------------------------------------- /src/predicates/isImmutable.js: -------------------------------------------------------------------------------- 1 | import { isCollection } from './isCollection'; 2 | import { isRecord } from './isRecord'; 3 | 4 | export function isImmutable(maybeImmutable) { 5 | return isCollection(maybeImmutable) || isRecord(maybeImmutable); 6 | } 7 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: http://EditorConfig.org 2 | 3 | root = true 4 | 5 | [*] 6 | end_of_line = lf 7 | insert_final_newline = true 8 | 9 | [*.{js,d.ts,json,html,md,sh}] 10 | charset = utf-8 11 | indent_style = space 12 | indent_size = 2 13 | -------------------------------------------------------------------------------- /type-definitions/flow-tests/.flowconfig: -------------------------------------------------------------------------------- 1 | [include] 2 | ../../ 3 | 4 | [options] 5 | module.name_mapper='^immutable$' -> '../../type-definitions/immutable.js.flow' 6 | 7 | [ignore] 8 | 💩 Only interested in testing these files directly in this repo. 9 | .*/node_modules/.* 10 | -------------------------------------------------------------------------------- /src/methods/toObject.js: -------------------------------------------------------------------------------- 1 | import assertNotInfinite from '../utils/assertNotInfinite'; 2 | 3 | export function toObject() { 4 | assertNotInfinite(this.size); 5 | const object = {}; 6 | this.__iterate((v, k) => { 7 | object[k] = v; 8 | }); 9 | return object; 10 | } 11 | -------------------------------------------------------------------------------- /type-definitions/ts-tests/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "dtslint/dtslint.json", 3 | "rules": { 4 | "no-var": false, 5 | "prefer-const": false, 6 | "no-useless-files": false, 7 | "no-duplicate-imports": false, 8 | "no-relative-import-in-test": false 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .*.haste_cache.* 2 | node_modules 3 | npm-debug.log 4 | yarn-error.log 5 | .DS_Store 6 | *~ 7 | *.swp 8 | .idea 9 | *.iml 10 | TODO 11 | publish 12 | /website/.next 13 | /website/out 14 | /website/public/sitemap.xml 15 | /website/public/robots.txt 16 | /gh-pages 17 | /npm 18 | /dist 19 | -------------------------------------------------------------------------------- /src/predicates/isCollection.js: -------------------------------------------------------------------------------- 1 | // Note: value is unchanged to not break immutable-devtools. 2 | export const IS_COLLECTION_SYMBOL = '@@__IMMUTABLE_ITERABLE__@@'; 3 | 4 | export function isCollection(maybeCollection) { 5 | return Boolean(maybeCollection && maybeCollection[IS_COLLECTION_SYMBOL]); 6 | } 7 | -------------------------------------------------------------------------------- /type-definitions/ts-tests/repeat.ts: -------------------------------------------------------------------------------- 1 | import { Repeat } from 'immutable'; 2 | 3 | { 4 | // #constructor 5 | 6 | // $ExpectType Indexed 7 | Repeat(0, 0); 8 | 9 | // $ExpectType Indexed 10 | Repeat('a', 0); 11 | 12 | // $ExpectError 13 | Repeat('a', 'b'); 14 | } 15 | -------------------------------------------------------------------------------- /src/methods/mergeDeep.js: -------------------------------------------------------------------------------- 1 | import { mergeDeepWithSources } from '../functional/merge'; 2 | 3 | export function mergeDeep(...iters) { 4 | return mergeDeepWithSources(this, iters); 5 | } 6 | 7 | export function mergeDeepWith(merger, ...iters) { 8 | return mergeDeepWithSources(this, iters, merger); 9 | } 10 | -------------------------------------------------------------------------------- /src/methods/mergeIn.js: -------------------------------------------------------------------------------- 1 | import { mergeWithSources } from '../functional/merge'; 2 | import { updateIn } from '../functional/updateIn'; 3 | import { emptyMap } from '../Map'; 4 | 5 | export function mergeIn(keyPath, ...iters) { 6 | return updateIn(this, keyPath, emptyMap(), m => mergeWithSources(m, iters)); 7 | } 8 | -------------------------------------------------------------------------------- /src/methods/README.md: -------------------------------------------------------------------------------- 1 | These files represent common methods on Collection types, each will contain 2 | references to "this" - expecting to be called as a prototypal method. 3 | 4 | They are separated into individual files to avoid circular dependencies when 5 | possible, and to allow their use in multiple different Collections. 6 | -------------------------------------------------------------------------------- /type-definitions/ts-tests/seq.ts: -------------------------------------------------------------------------------- 1 | import { Seq } from 'immutable'; 2 | 3 | { 4 | // #constructor 5 | 6 | // $ExpectType Indexed 7 | Seq([1, 2, 3]); 8 | } 9 | 10 | { 11 | // #size 12 | 13 | // $ExpectType number | undefined 14 | Seq().size; 15 | 16 | // $ExpectError 17 | Seq().size = 10; 18 | } 19 | -------------------------------------------------------------------------------- /website/next-sitemap.js: -------------------------------------------------------------------------------- 1 | const { getVersions } = require('./src/static/getVersions'); 2 | 3 | module.exports = { 4 | siteUrl: 'https://immutable-js.com', 5 | generateRobotsTxt: true, 6 | exclude: [ 7 | '/docs', 8 | ...getVersions() 9 | .slice(1) 10 | .map(version => `/docs/${version}/*`), 11 | ], 12 | }; 13 | -------------------------------------------------------------------------------- /src/utils/arrCopy.js: -------------------------------------------------------------------------------- 1 | // http://jsperf.com/copy-array-inline 2 | export default function arrCopy(arr, offset) { 3 | offset = offset || 0; 4 | const len = Math.max(0, arr.length - offset); 5 | const newArr = new Array(len); 6 | for (let ii = 0; ii < len; ii++) { 7 | newArr[ii] = arr[ii + offset]; 8 | } 9 | return newArr; 10 | } 11 | -------------------------------------------------------------------------------- /src/utils/quoteString.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Converts a value to a string, adding quotes if a string was provided. 3 | */ 4 | export default function quoteString(value) { 5 | try { 6 | return typeof value === 'string' ? JSON.stringify(value) : String(value); 7 | } catch (_ignoreError) { 8 | return JSON.stringify(value); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/methods/mergeDeepIn.js: -------------------------------------------------------------------------------- 1 | import { mergeDeepWithSources } from '../functional/merge'; 2 | import { updateIn } from '../functional/updateIn'; 3 | import { emptyMap } from '../Map'; 4 | 5 | export function mergeDeepIn(keyPath, ...iters) { 6 | return updateIn(this, keyPath, emptyMap(), m => 7 | mergeDeepWithSources(m, iters) 8 | ); 9 | } 10 | -------------------------------------------------------------------------------- /website/src/SVGSet.tsx: -------------------------------------------------------------------------------- 1 | import type { CSSProperties, ReactNode } from 'react'; 2 | 3 | export function SVGSet({ 4 | style, 5 | children, 6 | }: { 7 | style?: CSSProperties; 8 | children: ReactNode; 9 | }) { 10 | return ( 11 | 12 | {children} 13 | 14 | ); 15 | } 16 | -------------------------------------------------------------------------------- /type-definitions/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2015", 4 | "module": "commonjs", 5 | "sourceMap": true, 6 | "noImplicitAny": true, 7 | "noImplicitThis": true, 8 | "strictNullChecks": true, 9 | "strictFunctionTypes": true, 10 | "lib": ["es2015"], 11 | "types": [] 12 | }, 13 | "files": ["immutable.d.ts"] 14 | } 15 | -------------------------------------------------------------------------------- /website/src/isMobile.ts: -------------------------------------------------------------------------------- 1 | let _isMobile: boolean; 2 | export function isMobile() { 3 | if (_isMobile === undefined) { 4 | const isMobileMatch = 5 | typeof window !== 'undefined' && 6 | window.matchMedia && 7 | window.matchMedia('(max-device-width: 680px)'); 8 | _isMobile = isMobileMatch && isMobileMatch.matches; 9 | } 10 | return _isMobile; 11 | } 12 | -------------------------------------------------------------------------------- /resources/check-git-clean.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | 3 | if ! git diff --quiet; then echo " 4 | 5 | $(tput setf 4)The CI build resulted in additional changed files. 6 | Typically this is due to not running $(tput smul)npm test$(tput rmul) locally before 7 | submitting a pull request. 8 | 9 | The following changes were found:$(tput sgr0) 10 | "; 11 | 12 | git diff --exit-code; 13 | fi; 14 | -------------------------------------------------------------------------------- /src/functional/has.js: -------------------------------------------------------------------------------- 1 | import { isImmutable } from '../predicates/isImmutable'; 2 | import hasOwnProperty from '../utils/hasOwnProperty'; 3 | import isDataStructure from '../utils/isDataStructure'; 4 | 5 | export function has(collection, key) { 6 | return isImmutable(collection) 7 | ? collection.has(key) 8 | : isDataStructure(collection) && hasOwnProperty.call(collection, key); 9 | } 10 | -------------------------------------------------------------------------------- /src/utils/mixin.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Contributes additional methods to a constructor 3 | */ 4 | export default function mixin(ctor, methods) { 5 | const keyCopier = key => { 6 | ctor.prototype[key] = methods[key]; 7 | }; 8 | Object.keys(methods).forEach(keyCopier); 9 | Object.getOwnPropertySymbols && 10 | Object.getOwnPropertySymbols(methods).forEach(keyCopier); 11 | return ctor; 12 | } 13 | -------------------------------------------------------------------------------- /src/utils/shallowCopy.js: -------------------------------------------------------------------------------- 1 | import arrCopy from './arrCopy'; 2 | import hasOwnProperty from './hasOwnProperty'; 3 | 4 | export default function shallowCopy(from) { 5 | if (Array.isArray(from)) { 6 | return arrCopy(from); 7 | } 8 | const to = {}; 9 | for (const key in from) { 10 | if (hasOwnProperty.call(from, key)) { 11 | to[key] = from[key]; 12 | } 13 | } 14 | return to; 15 | } 16 | -------------------------------------------------------------------------------- /website/src/static/genMarkdownDoc.ts: -------------------------------------------------------------------------------- 1 | import { markdown } from './markdown'; 2 | import { getTypeDefs } from './getTypeDefs'; 3 | 4 | export function genMarkdownDoc(version: string, typeDefSource: string) { 5 | return markdown( 6 | typeDefSource 7 | .replace(/\n[^\n]+?Build Status[^\n]+?\n/, '\n') 8 | .replace('website/public', ''), 9 | { defs: getTypeDefs(version) } 10 | ); 11 | } 12 | -------------------------------------------------------------------------------- /src/functional/get.js: -------------------------------------------------------------------------------- 1 | import { isImmutable } from '../predicates/isImmutable'; 2 | import { has } from './has'; 3 | 4 | export function get(collection, key, notSetValue) { 5 | return isImmutable(collection) 6 | ? collection.get(key, notSetValue) 7 | : !has(collection, key) 8 | ? notSetValue 9 | : typeof collection.get === 'function' 10 | ? collection.get(key) 11 | : collection[key]; 12 | } 13 | -------------------------------------------------------------------------------- /__tests__/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "noEmit": true, 4 | // TODO: add additional strictness 5 | "strictNullChecks": true, 6 | "strictFunctionTypes": true, 7 | "target": "esnext", 8 | "moduleResolution": "node", 9 | "paths": { 10 | "immutable": ["../type-definitions/immutable.d.ts"], 11 | "jasmine-check": ["../resources/jasmine-check.d.ts"] 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /type-definitions/ts-tests/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2015", 4 | "module": "commonjs", 5 | "sourceMap": true, 6 | "strict": true, 7 | "types": [], 8 | "noEmit": true, 9 | "lib": [ 10 | "es2015" 11 | ], 12 | "baseUrl": "./", 13 | "paths": { 14 | "immutable": ["../../type-definitions/immutable.d.ts"] 15 | } 16 | }, 17 | "exclude": ["node_modules"] 18 | } 19 | -------------------------------------------------------------------------------- /type-definitions/flow-tests/predicates.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import { List } from 'immutable'; 3 | 4 | declare var mystery: mixed; 5 | 6 | // $FlowExpectedError[cannot-resolve-name] 7 | maybe.push('3'); 8 | 9 | if (mystery instanceof List) { 10 | maybe.push('3'); 11 | } 12 | 13 | // Note: Flow's support for %checks is still experimental. 14 | // Support this in the future. 15 | // if (List.isList(mystery)) { 16 | // mystery.push('3'); 17 | // } 18 | -------------------------------------------------------------------------------- /src/utils/coerceKeyPath.js: -------------------------------------------------------------------------------- 1 | import { isOrdered } from '../predicates/isOrdered'; 2 | import isArrayLike from './isArrayLike'; 3 | 4 | export default function coerceKeyPath(keyPath) { 5 | if (isArrayLike(keyPath) && typeof keyPath !== 'string') { 6 | return keyPath; 7 | } 8 | if (isOrdered(keyPath)) { 9 | return keyPath.toArray(); 10 | } 11 | throw new TypeError( 12 | 'Invalid keyPath: expected Ordered Collection or Array: ' + keyPath 13 | ); 14 | } 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Immutable Map 2 | 3 | This is a fork of [Immutable.js](https://immutable-js.com) that only includes "ImmutableMap" as an ESM package because it's the best immutable Map data structure for JavaScript. Documentation can be found on the [Immutable.js website](https://immutable-js.com). 4 | 5 | ## Installation 6 | 7 | ```bash 8 | pnpm install @nkzw/immutable-map 9 | ``` 10 | 11 | ## Usage 12 | 13 | ```js 14 | import ImmutableMap from '@nkzw/immutable-map'; 15 | ``` 16 | -------------------------------------------------------------------------------- /perf/toJS.js: -------------------------------------------------------------------------------- 1 | describe('toJS', () => { 2 | const array32 = []; 3 | for (let ii = 0; ii < 32; ii++) { 4 | array32[ii] = ii; 5 | } 6 | const list = Immutable.List(array32); 7 | 8 | it('List of 32', () => { 9 | Immutable.toJS(list); 10 | }); 11 | 12 | const obj32 = {}; 13 | for (let ii = 0; ii < 32; ii++) { 14 | obj32[ii] = ii; 15 | } 16 | const map = Immutable.Map(obj32); 17 | 18 | it('Map of 32', () => { 19 | Immutable.toJS(map); 20 | }); 21 | }); 22 | -------------------------------------------------------------------------------- /src/utils/isDataStructure.js: -------------------------------------------------------------------------------- 1 | import { isImmutable } from '../predicates/isImmutable'; 2 | import isPlainObj from './isPlainObj'; 3 | 4 | /** 5 | * Returns true if the value is a potentially-persistent data structure, either 6 | * provided by Immutable.js or a plain Array or Object. 7 | */ 8 | export default function isDataStructure(value) { 9 | return ( 10 | typeof value === 'object' && 11 | (isImmutable(value) || Array.isArray(value) || isPlainObj(value)) 12 | ); 13 | } 14 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | testRunner: 'jest-jasmine2', // See https://jestjs.io/blog/2021/05/25/jest-27#flipping-defaults as `jasmine-check` uses jasmine and not `jest-circus` 3 | moduleFileExtensions: ['js', 'ts'], 4 | resolver: '/resources/jestResolver.js', 5 | transform: { 6 | '^.+\\.(js|ts)$': '/resources/jestPreprocessor.js', 7 | }, 8 | testRegex: '/__tests__/.*\\.(ts|js)$', 9 | unmockedModulePathPatterns: ['./node_modules/react'], 10 | }; 11 | -------------------------------------------------------------------------------- /src/functional/getIn.js: -------------------------------------------------------------------------------- 1 | import coerceKeyPath from '../utils/coerceKeyPath'; 2 | import { NOT_SET } from '../TrieUtils'; 3 | import { get } from './get'; 4 | 5 | export function getIn(collection, searchKeyPath, notSetValue) { 6 | const keyPath = coerceKeyPath(searchKeyPath); 7 | let i = 0; 8 | while (i !== keyPath.length) { 9 | collection = get(collection, keyPath[i++], NOT_SET); 10 | if (collection === NOT_SET) { 11 | return notSetValue; 12 | } 13 | } 14 | return collection; 15 | } 16 | -------------------------------------------------------------------------------- /__tests__/Repeat.ts: -------------------------------------------------------------------------------- 1 | import { Repeat } from 'immutable'; 2 | 3 | describe('Repeat', () => { 4 | it('fixed repeat', () => { 5 | const v = Repeat('wtf', 3); 6 | expect(v.size).toBe(3); 7 | expect(v.first()).toBe('wtf'); 8 | expect(v.rest().toArray()).toEqual(['wtf', 'wtf']); 9 | expect(v.last()).toBe('wtf'); 10 | expect(v.butLast().toArray()).toEqual(['wtf', 'wtf']); 11 | expect(v.toArray()).toEqual(['wtf', 'wtf', 'wtf']); 12 | expect(v.join()).toEqual('wtf,wtf,wtf'); 13 | }); 14 | }); 15 | -------------------------------------------------------------------------------- /website/src/pages/_app.tsx: -------------------------------------------------------------------------------- 1 | import type { AppProps } from 'next/app'; 2 | import React from 'react'; 3 | import Head from 'next/head'; 4 | 5 | import '../../styles/globals.css'; 6 | import { RunkitEmbed } from '../RunkitEmbed'; 7 | 8 | export default function MyApp({ Component, pageProps }: AppProps) { 9 | return ( 10 | <> 11 | 12 | 13 | 14 | 15 | 16 | 17 | ); 18 | } 19 | -------------------------------------------------------------------------------- /website/src/DocHeader.tsx: -------------------------------------------------------------------------------- 1 | import { HeaderLinks, HeaderLogoLink } from './Header'; 2 | 3 | export function DocHeader({ 4 | versions, 5 | currentVersion, 6 | }: { 7 | versions: Array; 8 | currentVersion?: string; 9 | }) { 10 | return ( 11 |
12 |
13 |
14 | 15 | 16 |
17 |
18 |
19 | ); 20 | } 21 | -------------------------------------------------------------------------------- /type-definitions/flow-tests/es6-collections.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import { Map as ImmutableMap, Set as ImmutableSet } from 'immutable'; 4 | 5 | // Immutable.js collections 6 | var mapImmutable: ImmutableMap = ImmutableMap(); 7 | var setImmutable: ImmutableSet = ImmutableSet(); 8 | var deleteResultImmutable: ImmutableMap = mapImmutable.delete( 9 | 'foo' 10 | ); 11 | 12 | // ES6 collections 13 | var mapES6: Map = new Map(); 14 | var setES6: Set = new Set(); 15 | var deleteResultES6: boolean = mapES6.delete('foo'); 16 | -------------------------------------------------------------------------------- /website/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "strict": true, 8 | "forceConsistentCasingInFileNames": true, 9 | "noEmit": true, 10 | "esModuleInterop": true, 11 | "module": "esnext", 12 | "moduleResolution": "node", 13 | "resolveJsonModule": true, 14 | "isolatedModules": true, 15 | "jsx": "preserve" 16 | }, 17 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", "src/**/*.js"], 18 | "exclude": ["node_modules"] 19 | } 20 | -------------------------------------------------------------------------------- /type-definitions/ts-tests/es6-collections.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Map as ImmutableMap, 3 | Set as ImmutableSet, 4 | } from 'immutable'; 5 | 6 | // Immutable.js collections 7 | const mapImmutable: ImmutableMap = ImmutableMap(); 8 | const setImmutable: ImmutableSet = ImmutableSet(); 9 | 10 | // $ExpectType Map 11 | mapImmutable.delete('foo'); 12 | 13 | // ES6 collections 14 | const mapES6: Map = new Map(); 15 | const setES6: Set = new Set(); 16 | 17 | // $ExpectType boolean 18 | mapES6.delete('foo'); 19 | -------------------------------------------------------------------------------- /__tests__/__snapshots__/Record.ts.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`Record does not accept a Record as constructor 1`] = `"Can not call \`Record\` with an immutable Record as default values. Use a plain javascript object instead."`; 4 | 5 | exports[`Record does not accept a non object as constructor 1`] = `"Can not call \`Record\` with a non-object as default values. Use a plain javascript object instead."`; 6 | 7 | exports[`Record does not accept an immutable object that is not a Record as constructor 1`] = `"Can not call \`Record\` with an immutable Collection as default values. Use a plain javascript object instead."`; 8 | -------------------------------------------------------------------------------- /src/utils/isArrayLike.js: -------------------------------------------------------------------------------- 1 | export default function isArrayLike(value) { 2 | if (Array.isArray(value) || typeof value === 'string') { 3 | return true; 4 | } 5 | 6 | return ( 7 | value && 8 | typeof value === 'object' && 9 | Number.isInteger(value.length) && 10 | value.length >= 0 && 11 | (value.length === 0 12 | ? // Only {length: 0} is considered Array-like. 13 | Object.keys(value).length === 1 14 | : // An object is only Array-like if it has a property where the last value 15 | // in the array-like may be found (which could be undefined). 16 | value.hasOwnProperty(value.length - 1)) 17 | ); 18 | } 19 | -------------------------------------------------------------------------------- /website/src/static/stripUndefineds.ts: -------------------------------------------------------------------------------- 1 | export function stripUndefineds(obj: unknown) { 2 | if (Array.isArray(obj)) { 3 | for (const value of obj) { 4 | stripUndefineds(value); 5 | } 6 | } else if (isObj(obj)) { 7 | for (const prop in obj) { 8 | if (obj.hasOwnProperty(prop)) { 9 | const value = obj[prop]; 10 | if (value === undefined) { 11 | delete obj[prop]; 12 | } else { 13 | stripUndefineds(value); 14 | } 15 | } 16 | } 17 | } 18 | } 19 | 20 | function isObj(value: unknown): value is { [prop: string]: unknown } { 21 | return typeof value === 'object' && value !== null; 22 | } 23 | -------------------------------------------------------------------------------- /resources/jestResolver.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const pkg = require('../package.json'); 3 | 4 | module.exports = (request, options) => { 5 | if (request === 'immutable') { 6 | if (process.env.CI) { 7 | // In CI environment, test the real built file to be sure that the build is not broken 8 | return path.resolve(options.rootDir, pkg.main); 9 | } 10 | 11 | // In development mode, we want sourcemaps, live reload, etc., so point to the src/ directory 12 | return `${options.rootDir}/src/Immutable.js`; 13 | } 14 | 15 | // Call the defaultResolver, if we want to load non-immutable 16 | return options.defaultResolver(request, options); 17 | }; 18 | -------------------------------------------------------------------------------- /resources/rollup-config-es.mjs: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import buble from 'rollup-plugin-buble'; 3 | import commonjs from 'rollup-plugin-commonjs'; 4 | import json from 'rollup-plugin-json'; 5 | import stripBanner from 'rollup-plugin-strip-banner'; 6 | 7 | import copyright from './copyright.mjs'; 8 | 9 | const SRC_DIR = path.resolve('src'); 10 | const DIST_DIR = path.resolve('dist'); 11 | 12 | export default { 13 | input: path.join(SRC_DIR, 'Immutable.js'), 14 | output: { 15 | banner: copyright, 16 | name: 'Immutable', 17 | file: path.join(DIST_DIR, 'immutable.es.js'), 18 | format: 'es', 19 | sourcemap: false, 20 | }, 21 | plugins: [commonjs(), json(), stripBanner(), buble()], 22 | }; 23 | -------------------------------------------------------------------------------- /website/src/static/getVersions.js: -------------------------------------------------------------------------------- 1 | const { execSync } = require('child_process'); 2 | 3 | let versions; 4 | 5 | /** @returns {Array} */ 6 | function getVersions() { 7 | if (!versions) { 8 | const tags = execSync('git tag -l --sort=-creatordate', { 9 | encoding: 'utf8', 10 | }).split('\n'); 11 | const latestV4Tag = tags.find(t => t.match(/^v?4/)); 12 | const latestV3Tag = tags.find(t => t.match(/^v?3/)); 13 | versions = []; 14 | if (latestV4Tag) { 15 | versions.push(latestV4Tag); 16 | } 17 | if (latestV3Tag) { 18 | versions.push(latestV3Tag); 19 | } 20 | versions.push('latest@main'); 21 | } 22 | return versions; 23 | } 24 | 25 | exports.getVersions = getVersions; 26 | -------------------------------------------------------------------------------- /src/toJS.js: -------------------------------------------------------------------------------- 1 | import { Seq } from './Seq'; 2 | import { isCollection } from './predicates/isCollection'; 3 | import { isKeyed } from './predicates/isKeyed'; 4 | import isDataStructure from './utils/isDataStructure'; 5 | 6 | export function toJS(value) { 7 | if (!value || typeof value !== 'object') { 8 | return value; 9 | } 10 | if (!isCollection(value)) { 11 | if (!isDataStructure(value)) { 12 | return value; 13 | } 14 | value = Seq(value); 15 | } 16 | if (isKeyed(value)) { 17 | const result = {}; 18 | value.__iterate((v, k) => { 19 | result[k] = toJS(v); 20 | }); 21 | return result; 22 | } 23 | const result = []; 24 | value.__iterate(v => { 25 | result.push(toJS(v)); 26 | }); 27 | return result; 28 | } 29 | -------------------------------------------------------------------------------- /package-publish.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@nkzw/immutable-map", 3 | "version": "1.0.2", 4 | "description": "Immutable Map", 5 | "license": "MIT", 6 | "homepage": "https://immutable-js.com", 7 | "repository": { 8 | "type": "git", 9 | "url": "git://github.com/cpojer/immutable-js.git" 10 | }, 11 | "type": "module", 12 | "module": "./ImmutableMap.js", 13 | "exports": { 14 | ".": { 15 | "default": "./ImmutableMap.js" 16 | } 17 | }, 18 | "types": "./ImmutableMap.d.ts", 19 | "sideEffects": false, 20 | "keywords": [ 21 | "immutable", 22 | "persistent", 23 | "lazy", 24 | "data", 25 | "datastructure", 26 | "functional", 27 | "collection", 28 | "stateless", 29 | "sequence", 30 | "iteration" 31 | ] 32 | } 33 | -------------------------------------------------------------------------------- /src/Math.js: -------------------------------------------------------------------------------- 1 | export const imul = 2 | typeof Math.imul === 'function' && Math.imul(0xffffffff, 2) === -2 3 | ? Math.imul 4 | : function imul(a, b) { 5 | a |= 0; // int 6 | b |= 0; // int 7 | const c = a & 0xffff; 8 | const d = b & 0xffff; 9 | // Shift by 0 fixes the sign on the high part. 10 | return (c * d + ((((a >>> 16) * d + c * (b >>> 16)) << 16) >>> 0)) | 0; // int 11 | }; 12 | 13 | // v8 has an optimization for storing 31-bit signed numbers. 14 | // Values which have either 00 or 11 as the high order bits qualify. 15 | // This function drops the highest order bit in a signed number, maintaining 16 | // the sign bit. 17 | export function smi(i32) { 18 | return ((i32 >>> 1) & 0x40000000) | (i32 & 0xbfffffff); 19 | } 20 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | workflow_dispatch: ~ 5 | release: 6 | types: [published] 7 | 8 | jobs: 9 | build: 10 | name: 'Build & Publish to NPM' 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v2 14 | - uses: actions/setup-node@v2 15 | with: 16 | node-version: '16' 17 | registry-url: 'https://registry.npmjs.org' 18 | - uses: actions/cache@v2 19 | with: 20 | path: ~/.npm 21 | key: ${{ runner.OS }}-node-${{ hashFiles('**/package-lock.json') }} 22 | restore-keys: ${{ runner.OS }}-node- 23 | - run: npm ci 24 | - run: npm run build 25 | - run: cd npm && npm publish 26 | env: 27 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} 28 | -------------------------------------------------------------------------------- /src/utils/isPlainObj.js: -------------------------------------------------------------------------------- 1 | const toString = Object.prototype.toString; 2 | 3 | export default function isPlainObject(value) { 4 | // The base prototype's toString deals with Argument objects and native namespaces like Math 5 | if ( 6 | !value || 7 | typeof value !== 'object' || 8 | toString.call(value) !== '[object Object]' 9 | ) { 10 | return false; 11 | } 12 | 13 | const proto = Object.getPrototypeOf(value); 14 | if (proto === null) { 15 | return true; 16 | } 17 | 18 | // Iteratively going up the prototype chain is needed for cross-realm environments (differing contexts, iframes, etc) 19 | let parentProto = proto; 20 | let nextProto = Object.getPrototypeOf(proto); 21 | while (nextProto !== null) { 22 | parentProto = nextProto; 23 | nextProto = Object.getPrototypeOf(parentProto); 24 | } 25 | return parentProto === proto; 26 | } 27 | -------------------------------------------------------------------------------- /website/src/MarkdownContent.tsx: -------------------------------------------------------------------------------- 1 | import { memo, MouseEvent } from 'react'; 2 | import { useRouter } from 'next/router'; 3 | 4 | type Props = { 5 | contents: string; 6 | className?: string; 7 | }; 8 | 9 | // eslint-disable-next-line prefer-arrow-callback 10 | export const MarkdownContent = memo(function MarkdownContent({ 11 | contents, 12 | className, 13 | }) { 14 | const router = useRouter(); 15 | 16 | const handleClick = (event: MouseEvent) => { 17 | const link = event.target as HTMLAnchorElement; 18 | if (link.tagName === 'A' && link.target !== '_blank') { 19 | event.preventDefault(); 20 | router.push(link.href); 21 | } 22 | }; 23 | 24 | return ( 25 |
30 | ); 31 | }); 32 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["tslint:recommended", "tslint-config-prettier"], 3 | "rules": { 4 | "array-type": [true, "generic"], 5 | "quotemark": false, 6 | "no-reference": false, 7 | "no-namespace": false, 8 | "member-access": false, 9 | "interface-name": false, 10 | "member-ordering": false, 11 | "only-arrow-functions": false, 12 | "object-literal-sort-keys": false, 13 | "no-conditional-assignment": false, 14 | "one-variable-per-declaration": false, 15 | "max-classes-per-file": false, 16 | "space-before-function-paren": false, 17 | "trailing-comma": [ 18 | true, 19 | { 20 | "multiline": { 21 | "objects": "always", 22 | "arrays": "always", 23 | "typeLiterals": "always", 24 | "functions": "never" 25 | } 26 | } 27 | ], 28 | "no-console": false 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /website/src/collectMemberGroups.ts: -------------------------------------------------------------------------------- 1 | import type { InterfaceDefinition, MemberDefinition } from './TypeDefs'; 2 | 3 | export function collectMemberGroups( 4 | interfaceDef: InterfaceDefinition | undefined, 5 | showInGroups?: boolean, 6 | showInherited?: boolean 7 | ): Array<[groupTitle: string, members: Array]> { 8 | const groups: { [groupTitle: string]: Array } = {}; 9 | 10 | const members = interfaceDef?.members 11 | ? Object.values(interfaceDef.members) 12 | : []; 13 | 14 | if (!showInGroups) { 15 | members.sort((a, b) => (a.id > b.id ? 1 : -1)); 16 | } 17 | 18 | for (const member of members) { 19 | const groupTitle = (showInGroups && member.group) || ''; 20 | if (showInherited || !member.inherited) { 21 | (groups[groupTitle] || (groups[groupTitle] = [])).push(member); 22 | } 23 | } 24 | 25 | return Object.entries(groups); 26 | } 27 | -------------------------------------------------------------------------------- /resources/prepare-dist.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | 3 | # This script prepares an npm directory with files safe to publish to npm or the 4 | # npm git branch: 5 | # 6 | # "immutable": "git://github.com/immutable-js/immutable-js.git#npm" 7 | # 8 | 9 | # Create empty npm directory 10 | rm -rf npm 11 | mkdir -p npm 12 | 13 | # Copy over necessary files 14 | cp -r dist/immutable.es.js npm/immutable.js 15 | cp -r dist/immutable.d.ts npm/ 16 | cp README.md npm/ 17 | cp LICENSE npm/ 18 | 19 | # Ensure a vanilla package.json before deploying so other tools do not interpret 20 | # The built output as requiring any further transformation. 21 | node -e "var package = require('./package.json'); \ 22 | package = Object.fromEntries(Object.entries(package).filter(([key]) => package.publishKeys.includes(key))); \ 23 | package.type = \"module\"; \ 24 | require('fs').writeFileSync('./npm/package.json', JSON.stringify(package, null, 2));" 25 | -------------------------------------------------------------------------------- /src/functional/set.js: -------------------------------------------------------------------------------- 1 | import { isImmutable } from '../predicates/isImmutable'; 2 | import hasOwnProperty from '../utils/hasOwnProperty'; 3 | import isDataStructure from '../utils/isDataStructure'; 4 | import shallowCopy from '../utils/shallowCopy'; 5 | 6 | export function set(collection, key, value) { 7 | if (!isDataStructure(collection)) { 8 | throw new TypeError( 9 | 'Cannot update non-data-structure value: ' + collection 10 | ); 11 | } 12 | if (isImmutable(collection)) { 13 | if (!collection.set) { 14 | throw new TypeError( 15 | 'Cannot update immutable value without .set() method: ' + collection 16 | ); 17 | } 18 | return collection.set(key, value); 19 | } 20 | if (hasOwnProperty.call(collection, key) && value === collection[key]) { 21 | return collection; 22 | } 23 | const collectionCopy = shallowCopy(collection); 24 | collectionCopy[key] = value; 25 | return collectionCopy; 26 | } 27 | -------------------------------------------------------------------------------- /website/src/ArrowDown.tsx: -------------------------------------------------------------------------------- 1 | export const ArrowDown = ({ isActive }: { isActive: boolean }) => ( 2 | 11 | 12 | 25 | 26 | 27 | ); 28 | -------------------------------------------------------------------------------- /src/functional/remove.js: -------------------------------------------------------------------------------- 1 | import { isImmutable } from '../predicates/isImmutable'; 2 | import hasOwnProperty from '../utils/hasOwnProperty'; 3 | import isDataStructure from '../utils/isDataStructure'; 4 | import shallowCopy from '../utils/shallowCopy'; 5 | 6 | export function remove(collection, key) { 7 | if (!isDataStructure(collection)) { 8 | throw new TypeError( 9 | 'Cannot update non-data-structure value: ' + collection 10 | ); 11 | } 12 | if (isImmutable(collection)) { 13 | if (!collection.remove) { 14 | throw new TypeError( 15 | 'Cannot update immutable value without .remove() method: ' + collection 16 | ); 17 | } 18 | return collection.remove(key); 19 | } 20 | if (!hasOwnProperty.call(collection, key)) { 21 | return collection; 22 | } 23 | const collectionCopy = shallowCopy(collection); 24 | if (Array.isArray(collectionCopy)) { 25 | collectionCopy.splice(key, 1); 26 | } else { 27 | delete collectionCopy[key]; 28 | } 29 | return collectionCopy; 30 | } 31 | -------------------------------------------------------------------------------- /__tests__/find.ts: -------------------------------------------------------------------------------- 1 | import { Seq } from 'immutable'; 2 | 3 | import * as jasmineCheck from 'jasmine-check'; 4 | jasmineCheck.install(); 5 | 6 | describe('find', () => { 7 | it('find returns notSetValue when match is not found', () => { 8 | expect( 9 | Seq([1, 2, 3, 4, 5, 6]).find( 10 | function () { 11 | return false; 12 | }, 13 | null, 14 | 9 15 | ) 16 | ).toEqual(9); 17 | }); 18 | 19 | it('findEntry returns notSetValue when match is not found', () => { 20 | expect( 21 | Seq([1, 2, 3, 4, 5, 6]).findEntry( 22 | function () { 23 | return false; 24 | }, 25 | null, 26 | 9 27 | ) 28 | ).toEqual(9); 29 | }); 30 | 31 | it('findLastEntry returns notSetValue when match is not found', () => { 32 | expect( 33 | Seq([1, 2, 3, 4, 5, 6]).findLastEntry( 34 | function () { 35 | return false; 36 | }, 37 | null, 38 | 9 39 | ) 40 | ).toEqual(9); 41 | }); 42 | }); 43 | -------------------------------------------------------------------------------- /__tests__/interpose.ts: -------------------------------------------------------------------------------- 1 | import { Range } from 'immutable'; 2 | 3 | describe('interpose', () => { 4 | it('separates with a value', () => { 5 | const range = Range(10, 15); 6 | const interposed = range.interpose(0); 7 | expect(interposed.toArray()).toEqual([10, 0, 11, 0, 12, 0, 13, 0, 14]); 8 | }); 9 | 10 | it('can be iterated', () => { 11 | const range = Range(10, 15); 12 | const interposed = range.interpose(0); 13 | const values = interposed.values(); 14 | expect(values.next()).toEqual({ value: 10, done: false }); 15 | expect(values.next()).toEqual({ value: 0, done: false }); 16 | expect(values.next()).toEqual({ value: 11, done: false }); 17 | expect(values.next()).toEqual({ value: 0, done: false }); 18 | expect(values.next()).toEqual({ value: 12, done: false }); 19 | expect(values.next()).toEqual({ value: 0, done: false }); 20 | expect(values.next()).toEqual({ value: 13, done: false }); 21 | expect(values.next()).toEqual({ value: 0, done: false }); 22 | expect(values.next()).toEqual({ value: 14, done: false }); 23 | expect(values.next()).toEqual({ value: undefined, done: true }); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2014-present, Lee Byron and other contributors. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /__tests__/join.ts: -------------------------------------------------------------------------------- 1 | import { Seq } from 'immutable'; 2 | 3 | import * as jasmineCheck from 'jasmine-check'; 4 | jasmineCheck.install(); 5 | 6 | describe('join', () => { 7 | it('string-joins sequences with commas by default', () => { 8 | expect(Seq([1, 2, 3, 4, 5]).join()).toBe('1,2,3,4,5'); 9 | }); 10 | 11 | it('string-joins sequences with any string', () => { 12 | expect(Seq([1, 2, 3, 4, 5]).join('foo')).toBe('1foo2foo3foo4foo5'); 13 | }); 14 | 15 | it('string-joins sequences with empty string', () => { 16 | expect(Seq([1, 2, 3, 4, 5]).join('')).toBe('12345'); 17 | }); 18 | 19 | it('joins sparse-sequences like Array.join', () => { 20 | const a = [ 21 | 1, 22 | undefined, 23 | 2, 24 | undefined, 25 | 3, 26 | undefined, 27 | 4, 28 | undefined, 29 | 5, 30 | undefined, 31 | undefined, 32 | ]; 33 | expect(Seq(a).join()).toBe(a.join()); 34 | }); 35 | 36 | check.it( 37 | 'behaves the same as Array.join', 38 | [gen.array(gen.primitive), gen.primitive], 39 | (array, joiner) => { 40 | expect(Seq(array).join(joiner)).toBe(array.join(joiner)); 41 | } 42 | ); 43 | }); 44 | -------------------------------------------------------------------------------- /website/src/pages/index.tsx: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import Head from 'next/head'; 3 | 4 | import { Header } from '../Header'; 5 | import { MarkdownContent } from '../MarkdownContent'; 6 | import { ImmutableConsole } from '../ImmutableConsole'; 7 | import { genMarkdownDoc } from '../static/genMarkdownDoc'; 8 | import { getVersions } from '../static/getVersions'; 9 | 10 | type Props = { 11 | versions: Array; 12 | readme: string; 13 | }; 14 | 15 | export async function getStaticProps(): Promise<{ props: Props }> { 16 | const versions = getVersions(); 17 | const readme = genMarkdownDoc( 18 | versions[0], 19 | fs.readFileSync(`../README.md`, 'utf8') 20 | ); 21 | return { props: { versions, readme } }; 22 | } 23 | 24 | export default function Home({ versions, readme }: Props) { 25 | return ( 26 |
27 | 28 | Immutable.js 29 | 30 | 31 |
32 |
33 |
34 | 35 |
36 |
37 |
38 | ); 39 | } 40 | -------------------------------------------------------------------------------- /type-definitions/ts-tests/from-js.ts: -------------------------------------------------------------------------------- 1 | import { fromJS, List, Map } from 'immutable'; 2 | 3 | { 4 | // fromJS 5 | 6 | // $ExpectType Collection 7 | fromJS({}, (a: any, b: any) => b); 8 | 9 | // $ExpectType string 10 | fromJS('abc'); 11 | 12 | // $ExpectType List 13 | fromJS([0, 1, 2]); 14 | 15 | // $ExpectType List 16 | fromJS(List([0, 1, 2])); 17 | 18 | // $ExpectType Map<"b" | "a" | "c", number> 19 | fromJS({a: 0, b: 1, c: 2}); 20 | 21 | // $ExpectType Map 22 | fromJS(Map({a: 0, b: 1, c: 2})); 23 | 24 | // $ExpectType List> 25 | fromJS([{a: 0}]); 26 | 27 | // $ExpectType Map<"a", List> 28 | fromJS({a: [0]}); 29 | 30 | // $ExpectType List>> 31 | fromJS([[[0]]]); 32 | 33 | // $ExpectType Map<"a", Map<"b", Map<"c", number>>> 34 | fromJS({a: {b: {c: 0}}}); 35 | } 36 | 37 | { 38 | // fromJS in an array of function 39 | 40 | const create = [(data: any) => data, fromJS][1]; 41 | 42 | // $ExpectType any 43 | create({ a: 'A' }); 44 | 45 | const createConst = ([(data: any) => data, fromJS] as const)[1]; 46 | 47 | // $ExpectType Map<"a", string> 48 | createConst({ a: 'A' }); 49 | } 50 | -------------------------------------------------------------------------------- /website/src/pages/docs/index.tsx: -------------------------------------------------------------------------------- 1 | import Head from 'next/head'; 2 | import { useRouter } from 'next/router'; 3 | import { useEffect } from 'react'; 4 | 5 | import { getVersions } from '../../static/getVersions'; 6 | import { DocHeader } from '../../DocHeader'; 7 | 8 | type Props = { 9 | versions: Array; 10 | }; 11 | 12 | export async function getStaticProps(): Promise<{ props: Props }> { 13 | const versions = getVersions(); 14 | return { props: { versions } }; 15 | } 16 | 17 | export default function RedirectExistingDocs({ versions }: Props) { 18 | const router = useRouter(); 19 | useEffect(() => { 20 | const [, type, member] = global.window.location.hash?.split('/') || []; 21 | let route = `/docs/${versions[0]}`; 22 | if (type) { 23 | route += `/${type}`; 24 | } 25 | if (member) { 26 | route += `#${member}`; 27 | } 28 | router.replace(route); 29 | }, [versions, router]); 30 | 31 | return ( 32 |
33 | 34 | Documentation — Immutable.js 35 | 36 | 37 |
38 |
Redirecting...
39 |
40 |
41 | ); 42 | } 43 | -------------------------------------------------------------------------------- /type-definitions/ts-tests/empty.ts: -------------------------------------------------------------------------------- 1 | import { Seq, Collection } from 'immutable'; 2 | 3 | { 4 | // Typed empty seqs 5 | 6 | // $ExpectType Seq 7 | Seq(); 8 | 9 | // $ExpectType Seq 10 | Seq(); 11 | 12 | // $ExpectType Indexed 13 | Seq.Indexed(); 14 | 15 | // $ExpectType Indexed 16 | Seq.Indexed(); 17 | 18 | // $ExpectType Keyed 19 | Seq.Keyed(); 20 | 21 | // $ExpectType Keyed 22 | Seq.Keyed(); 23 | 24 | // $ExpectType Set 25 | Seq.Set(); 26 | 27 | // $ExpectType Set 28 | Seq.Set(); 29 | } 30 | 31 | { 32 | // Typed empty collection 33 | 34 | // $ExpectType Collection 35 | Collection(); 36 | 37 | // $ExpectType Collection 38 | Collection(); 39 | 40 | // $ExpectType Indexed 41 | Collection.Indexed(); 42 | 43 | // $ExpectType Indexed 44 | Collection.Indexed(); 45 | 46 | // $ExpectType Keyed 47 | Collection.Keyed(); 48 | 49 | // $ExpectType Keyed 50 | Collection.Keyed(); 51 | 52 | // $ExpectType Set 53 | Collection.Set(); 54 | 55 | // $ExpectType Set 56 | Collection.Set(); 57 | } 58 | -------------------------------------------------------------------------------- /type-definitions/ts-tests/groupBy.ts: -------------------------------------------------------------------------------- 1 | import { List, Map, OrderedMap, Record, Set, Seq, Stack, OrderedSet, DeepCopy, Collection } from 'immutable'; 2 | 3 | { 4 | // $ExpectType Map> 5 | Collection(['a', 'b', 'c', 'a']).groupBy(v => v); 6 | 7 | // $ExpectType Map> 8 | Collection({ a: 1, b: 2, c: 3, d: 1 }).groupBy(v => `key-${v}`); 9 | 10 | // $ExpectType Map> 11 | List(['a', 'b', 'c', 'a']).groupBy(v => v); 12 | 13 | // $ExpectType Map> 14 | Seq(['a', 'b', 'c', 'a']).groupBy(v => v); 15 | 16 | // $ExpectType Map> 17 | Seq({ a: 1, b: 2, c: 3, d: 1 }).groupBy(v => `key-${v}`); 18 | 19 | // $ExpectType Map> 20 | Set(['a', 'b', 'c', 'a']).groupBy(v => v); 21 | 22 | // $ExpectType Map> 23 | Stack(['a', 'b', 'c', 'a']).groupBy(v => v); 24 | 25 | // $ExpectType Map> 26 | OrderedSet(['a', 'b', 'c', 'a']).groupBy(v => v); 27 | 28 | // $ExpectType Map> 29 | Map({ a: 1, b: 2, c: 3, d: 1 }).groupBy(v => `key-${v}`); 30 | 31 | // $ExpectType Map> 32 | OrderedMap({ a: 1, b: 2, c: 3, d: 1 }).groupBy(v => `key-${v}`); 33 | } 34 | -------------------------------------------------------------------------------- /__tests__/get.ts: -------------------------------------------------------------------------------- 1 | import { Range } from 'immutable'; 2 | 3 | describe('get', () => { 4 | it('gets any index', () => { 5 | const seq = Range(0, 100); 6 | expect(seq.get(20)).toBe(20); 7 | }); 8 | 9 | it('gets first', () => { 10 | const seq = Range(0, 100); 11 | expect(seq.first()).toBe(0); 12 | }); 13 | 14 | it('gets last', () => { 15 | const seq = Range(0, 100); 16 | expect(seq.last()).toBe(99); 17 | }); 18 | 19 | it('gets any index after reversing', () => { 20 | const seq = Range(0, 100).reverse(); 21 | expect(seq.get(20)).toBe(79); 22 | }); 23 | 24 | it('gets first after reversing', () => { 25 | const seq = Range(0, 100).reverse(); 26 | expect(seq.first()).toBe(99); 27 | }); 28 | 29 | it('gets last after reversing', () => { 30 | const seq = Range(0, 100).reverse(); 31 | expect(seq.last()).toBe(0); 32 | }); 33 | 34 | it('gets any index when size is unknown', () => { 35 | const seq = Range(0, 100).filter(x => x % 2 === 1); 36 | expect(seq.get(20)).toBe(41); 37 | }); 38 | 39 | it('gets first when size is unknown', () => { 40 | const seq = Range(0, 100).filter(x => x % 2 === 1); 41 | expect(seq.first()).toBe(1); 42 | }); 43 | 44 | it('gets last when size is unknown', () => { 45 | const seq = Range(0, 100).filter(x => x % 2 === 1); 46 | expect(seq.last()).toBe(99); // Note: this is O(N) 47 | }); 48 | }); 49 | -------------------------------------------------------------------------------- /resources/rollup-config.mjs: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import path from 'path'; 3 | import { minify } from 'uglify-js'; 4 | import buble from 'rollup-plugin-buble'; 5 | import commonjs from 'rollup-plugin-commonjs'; 6 | import json from 'rollup-plugin-json'; 7 | import saveLicense from 'uglify-save-license'; 8 | import stripBanner from 'rollup-plugin-strip-banner'; 9 | 10 | import copyright from './copyright.mjs'; 11 | 12 | const SRC_DIR = path.resolve('src'); 13 | const DIST_DIR = path.resolve('dist'); 14 | 15 | function uglify() { 16 | return { 17 | name: 'uglify', 18 | async renderChunk(code) { 19 | const result = minify(code, { 20 | mangle: { toplevel: true }, 21 | output: { max_line_len: 2048, comments: saveLicense }, 22 | compress: { comparisons: true, pure_getters: true, unsafe: true }, 23 | }); 24 | 25 | if (!fs.existsSync(DIST_DIR)) { 26 | fs.mkdirSync(DIST_DIR); 27 | } 28 | 29 | fs.writeFileSync( 30 | path.join(DIST_DIR, 'immutable.min.js'), 31 | result.code, 32 | 'utf8' 33 | ); 34 | return code; 35 | }, 36 | }; 37 | } 38 | 39 | export default { 40 | input: path.join(SRC_DIR, 'Immutable.js'), 41 | output: { 42 | banner: copyright, 43 | name: 'Immutable', 44 | exports: 'named', 45 | file: path.join(DIST_DIR, 'immutable.js'), 46 | format: 'umd', 47 | sourcemap: false, 48 | plugins: [uglify()], 49 | }, 50 | plugins: [commonjs(), json(), stripBanner(), buble()], 51 | }; 52 | -------------------------------------------------------------------------------- /src/methods/merge.js: -------------------------------------------------------------------------------- 1 | import { KeyedCollection } from '../Seq'; 2 | import { NOT_SET } from '../TrieUtils'; 3 | import { update } from '../functional/update'; 4 | 5 | export function merge(...iters) { 6 | return mergeIntoKeyedWith(this, iters); 7 | } 8 | 9 | export function mergeWith(merger, ...iters) { 10 | if (typeof merger !== 'function') { 11 | throw new TypeError('Invalid merger function: ' + merger); 12 | } 13 | return mergeIntoKeyedWith(this, iters, merger); 14 | } 15 | 16 | function mergeIntoKeyedWith(collection, collections, merger) { 17 | const iters = []; 18 | for (let ii = 0; ii < collections.length; ii++) { 19 | const collection = KeyedCollection(collections[ii]); 20 | if (collection.size !== 0) { 21 | iters.push(collection); 22 | } 23 | } 24 | if (iters.length === 0) { 25 | return collection; 26 | } 27 | if ( 28 | collection.toSeq().size === 0 && 29 | !collection.__ownerID && 30 | iters.length === 1 31 | ) { 32 | return collection.constructor(iters[0]); 33 | } 34 | return collection.withMutations(collection => { 35 | const mergeIntoCollection = merger 36 | ? (value, key) => { 37 | update(collection, key, NOT_SET, oldVal => 38 | oldVal === NOT_SET ? value : merger(oldVal, value, key) 39 | ); 40 | } 41 | : (value, key) => { 42 | collection.set(key, value); 43 | }; 44 | for (let ii = 0; ii < iters.length; ii++) { 45 | iters[ii].forEach(mergeIntoCollection); 46 | } 47 | }); 48 | } 49 | -------------------------------------------------------------------------------- /src/fromJS.js: -------------------------------------------------------------------------------- 1 | import { Seq } from './Seq'; 2 | import { hasIterator } from './Iterator'; 3 | import { isImmutable } from './predicates/isImmutable'; 4 | import { isIndexed } from './predicates/isIndexed'; 5 | import { isKeyed } from './predicates/isKeyed'; 6 | import isArrayLike from './utils/isArrayLike'; 7 | import isPlainObj from './utils/isPlainObj'; 8 | 9 | export function fromJS(value, converter) { 10 | return fromJSWith( 11 | [], 12 | converter || defaultConverter, 13 | value, 14 | '', 15 | converter && converter.length > 2 ? [] : undefined, 16 | { '': value } 17 | ); 18 | } 19 | 20 | function fromJSWith(stack, converter, value, key, keyPath, parentValue) { 21 | if ( 22 | typeof value !== 'string' && 23 | !isImmutable(value) && 24 | (isArrayLike(value) || hasIterator(value) || isPlainObj(value)) 25 | ) { 26 | if (~stack.indexOf(value)) { 27 | throw new TypeError('Cannot convert circular structure to Immutable'); 28 | } 29 | stack.push(value); 30 | keyPath && key !== '' && keyPath.push(key); 31 | const converted = converter.call( 32 | parentValue, 33 | key, 34 | Seq(value).map((v, k) => 35 | fromJSWith(stack, converter, v, k, keyPath, value) 36 | ), 37 | keyPath && keyPath.slice() 38 | ); 39 | stack.pop(); 40 | keyPath && keyPath.pop(); 41 | return converted; 42 | } 43 | return value; 44 | } 45 | 46 | function defaultConverter(k, v) { 47 | // Effectively the opposite of "Collection.toSeq()" 48 | return isIndexed(v) ? v.toList() : isKeyed(v) ? v.toMap() : v.toSet(); 49 | } 50 | -------------------------------------------------------------------------------- /__tests__/IndexedSeq.ts: -------------------------------------------------------------------------------- 1 | import { Seq } from 'immutable'; 2 | 3 | import * as jasmineCheck from 'jasmine-check'; 4 | jasmineCheck.install(); 5 | 6 | describe('IndexedSequence', () => { 7 | it('maintains skipped offset', () => { 8 | const seq = Seq(['A', 'B', 'C', 'D', 'E']); 9 | 10 | // This is what we expect for IndexedSequences 11 | const operated = seq.skip(1); 12 | expect(operated.entrySeq().toArray()).toEqual([ 13 | [0, 'B'], 14 | [1, 'C'], 15 | [2, 'D'], 16 | [3, 'E'], 17 | ]); 18 | 19 | expect(operated.first()).toEqual('B'); 20 | }); 21 | 22 | it('reverses correctly', () => { 23 | const seq = Seq(['A', 'B', 'C', 'D', 'E']); 24 | 25 | // This is what we expect for IndexedSequences 26 | const operated = seq.reverse(); 27 | expect(operated.get(0)).toEqual('E'); 28 | expect(operated.get(1)).toEqual('D'); 29 | expect(operated.get(4)).toEqual('A'); 30 | 31 | expect(operated.first()).toEqual('E'); 32 | expect(operated.last()).toEqual('A'); 33 | }); 34 | 35 | it('negative indexes correctly', () => { 36 | const seq = Seq(['A', 'B', 'C', 'D', 'E']); 37 | 38 | expect(seq.first()).toEqual('A'); 39 | expect(seq.last()).toEqual('E'); 40 | expect(seq.get(-0)).toEqual('A'); 41 | expect(seq.get(2)).toEqual('C'); 42 | expect(seq.get(-2)).toEqual('D'); 43 | 44 | const indexes = seq.keySeq(); 45 | expect(indexes.first()).toEqual(0); 46 | expect(indexes.last()).toEqual(4); 47 | expect(indexes.get(-0)).toEqual(0); 48 | expect(indexes.get(2)).toEqual(2); 49 | expect(indexes.get(-2)).toEqual(3); 50 | }); 51 | }); 52 | -------------------------------------------------------------------------------- /website/src/DocSearch.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from 'react'; 2 | 3 | export function DocSearch() { 4 | const [enabled, setEnabled] = useState(null); 5 | 6 | useEffect(() => { 7 | const script = document.createElement('script'); 8 | const firstScript = document.getElementsByTagName('script')[0]; 9 | script.src = 10 | 'https://cdn.jsdelivr.net/npm/docsearch.js@2.5.2/dist/cdn/docsearch.min.js'; 11 | script.addEventListener( 12 | 'load', 13 | () => { 14 | // Initialize Algolia search. 15 | // @ts-ignore 16 | if (window.docsearch) { 17 | // @ts-ignore 18 | window.docsearch({ 19 | apiKey: '83f61f865ef4cb682e0432410c2f7809', 20 | indexName: 'immutable_js', 21 | inputSelector: '#algolia-docsearch', 22 | }); 23 | setEnabled(true); 24 | } else { 25 | setEnabled(false); 26 | } 27 | }, 28 | false 29 | ); 30 | firstScript?.parentNode?.insertBefore(script, firstScript); 31 | 32 | const link = document.createElement('link'); 33 | const firstLink = document.getElementsByTagName('link')[0]; 34 | link.rel = 'stylesheet'; 35 | link.href = 36 | 'https://cdn.jsdelivr.net/npm/docsearch.js@2.5.2/dist/cdn/docsearch.min.css'; 37 | firstLink?.parentNode?.insertBefore(link, firstLink); 38 | }, []); 39 | 40 | if (enabled === false) return null; 41 | 42 | return ( 43 | 50 | ); 51 | } 52 | -------------------------------------------------------------------------------- /website/src/DocOverview.tsx: -------------------------------------------------------------------------------- 1 | import Link from 'next/link'; 2 | import { MarkdownContent } from './MarkdownContent'; 3 | import type { TypeDefs, TypeDoc } from './TypeDefs'; 4 | 5 | export type OverviewData = { 6 | doc: TypeDoc | null; 7 | api: Array; 8 | }; 9 | 10 | type APIMember = { 11 | label: string; 12 | url: string; 13 | synopsis?: string; 14 | }; 15 | 16 | // Static use only 17 | export function getOverviewData(defs: TypeDefs): OverviewData { 18 | return { 19 | doc: defs.doc || null, 20 | api: Object.values(defs.types).map(def => { 21 | const member: APIMember = { label: def.label, url: def.url }; 22 | const doc = def.doc || def.call?.doc; 23 | if (doc?.synopsis) { 24 | member.synopsis = doc?.synopsis; 25 | } 26 | return member; 27 | }), 28 | }; 29 | } 30 | 31 | export function DocOverview({ data }: { data: OverviewData }) { 32 | return ( 33 |
34 | {data.doc && ( 35 |
36 | 37 | {data.doc.description && ( 38 | 39 | )} 40 |
41 | )} 42 | 43 |

API

44 | 45 | {data.api.map(member => ( 46 |
47 |

48 | {member.label} 49 |

50 | {member.synopsis && ( 51 | 52 | )} 53 |
54 | ))} 55 |
56 | ); 57 | } 58 | -------------------------------------------------------------------------------- /type-definitions/flow-tests/covariance.js: -------------------------------------------------------------------------------- 1 | //@flow 2 | 3 | import { List, Map, Set, Stack, OrderedMap, OrderedSet } from 'immutable'; 4 | 5 | class A { 6 | x: number; 7 | } 8 | class B extends A { 9 | y: string; 10 | } 11 | class C { 12 | z: string; 13 | } 14 | 15 | // List covariance 16 | declare var listOfB: List; 17 | var listOfA: List = listOfB; 18 | listOfA = List([new B()]); 19 | // $FlowExpectedError[incompatible-type-arg] 20 | var listOfC: List = listOfB; 21 | 22 | // Map covariance 23 | declare var mapOfB: Map; 24 | var mapOfA: Map = mapOfB; 25 | mapOfA = Map({ b: new B() }); 26 | // $FlowExpectedError[incompatible-type-arg] 27 | var mapOfC: Map = mapOfB; 28 | 29 | // Set covariance 30 | declare var setOfB: Set; 31 | var setOfA: Set = setOfB; 32 | setOfA = Set([new B()]); 33 | // $FlowExpectedError[incompatible-type-arg] 34 | var setOfC: Set = setOfB; 35 | 36 | // Stack covariance 37 | declare var stackOfB: Stack; 38 | var stackOfA: Stack = stackOfB; 39 | stackOfA = Stack([new B()]); 40 | // $FlowExpectedError[incompatible-type-arg] 41 | var stackOfC: Stack = stackOfB; 42 | 43 | // OrderedMap covariance 44 | declare var orderedMapOfB: OrderedMap; 45 | var orderedMapOfA: OrderedMap = orderedMapOfB; 46 | orderedMapOfA = OrderedMap({ b: new B() }); 47 | // $FlowExpectedError[incompatible-type-arg] 48 | var orderedMapOfC: OrderedMap = orderedMapOfB; 49 | 50 | // OrderedSet covariance 51 | declare var orderedSetOfB: OrderedSet; 52 | var orderedSetOfA: OrderedSet = orderedSetOfB; 53 | orderedSetOfA = OrderedSet([new B()]); 54 | // $FlowExpectedError[incompatible-type-arg] 55 | var orderedSetOfC: OrderedSet = orderedSetOfB; 56 | -------------------------------------------------------------------------------- /type-definitions/ts-tests/functional.ts: -------------------------------------------------------------------------------- 1 | import { 2 | get, 3 | has, 4 | set, 5 | remove, 6 | update, 7 | } from 'immutable'; 8 | 9 | { 10 | // get 11 | 12 | // $ExpectType number | undefined 13 | get([1, 2, 3], 0); 14 | 15 | // $ExpectType number | "a" 16 | get([1, 2, 3], 0, 'a'); 17 | 18 | // $ExpectType number | undefined 19 | get({ x: 10, y: 20 }, 'x'); 20 | 21 | // $ExpectType number | "missing" 22 | get({ x: 10, y: 20 }, 'z', 'missing'); 23 | } 24 | 25 | { 26 | // has 27 | 28 | // $ExpectType boolean 29 | has([1, 2, 3], 0); 30 | 31 | // $ExpectType boolean 32 | has({ x: 10, y: 20 }, 'x'); 33 | } 34 | 35 | { 36 | // set 37 | 38 | // $ExpectType number[] 39 | set([1, 2, 3], 0, 10); 40 | 41 | // $ExpectError 42 | set([1, 2, 3], 0, 'a'); 43 | 44 | // $ExpectError 45 | set([1, 2, 3], 'a', 0); 46 | 47 | // $ExpectType { x: number; y: number; } 48 | set({ x: 10, y: 20 }, 'x', 100); 49 | 50 | // $ExpectError 51 | set({ x: 10, y: 20 }, 'x', 'a'); 52 | } 53 | 54 | { 55 | // remove 56 | 57 | // $ExpectType number[] 58 | remove([1, 2, 3], 0); 59 | 60 | // $ExpectType { x: number; y: number; } 61 | remove({ x: 10, y: 20 }, 'x'); 62 | } 63 | 64 | { 65 | // update 66 | 67 | // $ExpectType number[] 68 | update([1, 2, 3], 0, (v: number) => v + 1); 69 | 70 | // $ExpectError 71 | update([1, 2, 3], 0, 1); 72 | 73 | // $ExpectError 74 | update([1, 2, 3], 0, (v: string) => v + 'a'); 75 | 76 | // $ExpectError 77 | update([1, 2, 3], 'a', (v: number) => v + 1); 78 | 79 | // $ExpectType { x: number; y: number; } 80 | update({ x: 10, y: 20 }, 'x', (v: number) => v + 1); 81 | 82 | // $ExpectError 83 | update({ x: 10, y: 20 }, 'x', (v: string) => v + 'a'); 84 | } 85 | -------------------------------------------------------------------------------- /__tests__/ListJS.js: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line import/no-unresolved -- immutable is resolve by jest resolver 2 | import { List } from 'immutable'; 3 | 4 | const NON_NUMBERS = { 5 | array: ['not', 'a', 'number'], 6 | NaN: NaN, 7 | object: { not: 'a number' }, 8 | string: 'not a number', 9 | }; 10 | 11 | describe('List', () => { 12 | describe('setSize()', () => { 13 | Object.keys(NON_NUMBERS).forEach(type => { 14 | const nonNumber = NON_NUMBERS[type]; 15 | it(`considers a size argument of type '${type}' to be zero`, () => { 16 | const v1 = List.of(1, 2, 3); 17 | const v2 = v1.setSize(nonNumber); 18 | expect(v2.size).toBe(0); 19 | }); 20 | }); 21 | }); 22 | describe('slice()', () => { 23 | // Mimic the behavior of Array::slice() 24 | // http://www.ecma-international.org/ecma-262/6.0/#sec-array.prototype.slice 25 | Object.keys(NON_NUMBERS).forEach(type => { 26 | const nonNumber = NON_NUMBERS[type]; 27 | it(`considers a begin argument of type '${type}' to be zero`, () => { 28 | const v1 = List.of('a', 'b', 'c'); 29 | const v2 = v1.slice(nonNumber, 2); 30 | expect(v2.size).toBe(2); 31 | expect(v2.first()).toBe('a'); 32 | expect(v2.rest().size).toBe(1); 33 | expect(v2.last()).toBe('b'); 34 | expect(v2.butLast().size).toBe(1); 35 | }); 36 | it(`considers an end argument of type '${type}' to be zero`, () => { 37 | const v1 = List.of('a', 'b', 'c'); 38 | const v2 = v1.slice(0, nonNumber); 39 | expect(v2.size).toBe(0); 40 | expect(v2.first()).toBe(undefined); 41 | expect(v2.rest().size).toBe(0); 42 | expect(v2.last()).toBe(undefined); 43 | expect(v2.butLast().size).toBe(0); 44 | }); 45 | }); 46 | }); 47 | }); 48 | -------------------------------------------------------------------------------- /__tests__/Predicates.ts: -------------------------------------------------------------------------------- 1 | import { 2 | is, 3 | isImmutable, 4 | isValueObject, 5 | List, 6 | Map, 7 | Set, 8 | Stack, 9 | } from 'immutable'; 10 | 11 | describe('isImmutable', () => { 12 | it('behaves as advertised', () => { 13 | expect(isImmutable([])).toBe(false); 14 | expect(isImmutable({})).toBe(false); 15 | expect(isImmutable(Map())).toBe(true); 16 | expect(isImmutable(List())).toBe(true); 17 | expect(isImmutable(Set())).toBe(true); 18 | expect(isImmutable(Stack())).toBe(true); 19 | expect(isImmutable(Map().asMutable())).toBe(true); 20 | }); 21 | }); 22 | 23 | describe('isValueObject', () => { 24 | it('behaves as advertised', () => { 25 | expect(isValueObject(null)).toBe(false); 26 | expect(isValueObject(123)).toBe(false); 27 | expect(isValueObject('abc')).toBe(false); 28 | expect(isValueObject([])).toBe(false); 29 | expect(isValueObject({})).toBe(false); 30 | expect(isValueObject(Map())).toBe(true); 31 | expect(isValueObject(List())).toBe(true); 32 | expect(isValueObject(Set())).toBe(true); 33 | expect(isValueObject(Stack())).toBe(true); 34 | expect(isValueObject(Map().asMutable())).toBe(true); 35 | }); 36 | 37 | it('works on custom types', () => { 38 | class MyValueType { 39 | v: any; 40 | 41 | constructor(val) { 42 | this.v = val; 43 | } 44 | 45 | equals(other) { 46 | return Boolean(other && this.v === other.v); 47 | } 48 | 49 | hashCode() { 50 | return this.v; 51 | } 52 | } 53 | 54 | expect(isValueObject(new MyValueType(123))).toBe(true); 55 | expect(is(new MyValueType(123), new MyValueType(123))).toBe(true); 56 | expect(Set().add(new MyValueType(123)).add(new MyValueType(123)).size).toBe( 57 | 1 58 | ); 59 | }); 60 | }); 61 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 33 | 34 | ### What happened 35 | 36 | 38 | 39 | ### How to reproduce 40 | 41 | 43 | -------------------------------------------------------------------------------- /__tests__/hash.ts: -------------------------------------------------------------------------------- 1 | import { hash } from 'immutable'; 2 | 3 | import * as jasmineCheck from 'jasmine-check'; 4 | jasmineCheck.install(); 5 | 6 | describe('hash', () => { 7 | it('stable hash of well known values', () => { 8 | expect(hash(true)).toBe(0x42108421); 9 | expect(hash(false)).toBe(0x42108420); 10 | expect(hash(0)).toBe(0); 11 | expect(hash(null)).toBe(0x42108422); 12 | expect(hash(undefined)).toBe(0x42108423); 13 | expect(hash('a')).toBe(97); 14 | expect(hash('immutable-js')).toBe(510203252); 15 | expect(hash(123)).toBe(123); 16 | }); 17 | 18 | it('generates different hashes for decimal values', () => { 19 | expect(hash(123.456)).toBe(884763256); 20 | expect(hash(123.4567)).toBe(887769707); 21 | }); 22 | 23 | it('generates different hashes for different objects', () => { 24 | const objA = {}; 25 | const objB = {}; 26 | expect(hash(objA)).toBe(hash(objA)); 27 | expect(hash(objA)).not.toBe(hash(objB)); 28 | }); 29 | 30 | it('generates different hashes for different symbols', () => { 31 | const symA = Symbol(); 32 | const symB = Symbol(); 33 | expect(hash(symA)).toBe(hash(symA)); 34 | expect(hash(symA)).not.toBe(hash(symB)); 35 | }); 36 | 37 | it('generates different hashes for different functions', () => { 38 | const funA = () => { 39 | return; 40 | }; 41 | const funB = () => { 42 | return; 43 | }; 44 | expect(hash(funA)).toBe(hash(funA)); 45 | expect(hash(funA)).not.toBe(hash(funB)); 46 | }); 47 | 48 | const genValue = gen.oneOf([gen.string, gen.int]); 49 | 50 | check.it('generates unsigned 31-bit integers', [genValue], value => { 51 | const hashVal = hash(value); 52 | expect(Number.isInteger(hashVal)).toBe(true); 53 | expect(hashVal).toBeGreaterThan(-Math.pow(2, 31)); 54 | expect(hashVal).toBeLessThan(Math.pow(2, 31)); 55 | }); 56 | }); 57 | -------------------------------------------------------------------------------- /type-definitions/ts-tests/covariance.ts: -------------------------------------------------------------------------------- 1 | import { List, Map, OrderedMap, OrderedSet, Set, Stack } from 'immutable'; 2 | 3 | class A { 4 | x: number; 5 | constructor() { 6 | this.x = 1; 7 | } 8 | } 9 | class B extends A { 10 | y: string; 11 | constructor() { 12 | super(); 13 | this.y = 'B'; 14 | } 15 | } 16 | class C { 17 | z: string; 18 | constructor() { 19 | this.z = 'C'; 20 | } 21 | } 22 | 23 | // List covariance 24 | let listOfB: List = List(); 25 | let listOfA: List = listOfB; 26 | 27 | // $ExpectType List 28 | listOfA = List([new B()]); 29 | 30 | // $ExpectError 31 | let listOfC: List = listOfB; 32 | 33 | // Map covariance 34 | declare var mapOfB: Map; 35 | let mapOfA: Map = mapOfB; 36 | 37 | // $ExpectType Map 38 | mapOfA = Map({ b: new B() }); 39 | 40 | // $ExpectError 41 | let mapOfC: Map = mapOfB; 42 | 43 | // Set covariance 44 | declare var setOfB: Set; 45 | let setOfA: Set = setOfB; 46 | 47 | // $ExpectType Set 48 | setOfA = Set([new B()]); 49 | // $ExpectError 50 | let setOfC: Set = setOfB; 51 | 52 | // Stack covariance 53 | declare var stackOfB: Stack; 54 | let stackOfA: Stack = stackOfB; 55 | // $ExpectType Stack 56 | stackOfA = Stack([new B()]); 57 | // $ExpectError 58 | let stackOfC: Stack = stackOfB; 59 | 60 | // OrderedMap covariance 61 | declare var orderedMapOfB: OrderedMap; 62 | let orderedMapOfA: OrderedMap = orderedMapOfB; 63 | // $ExpectType OrderedMap 64 | orderedMapOfA = OrderedMap({ b: new B() }); 65 | // $ExpectError 66 | let orderedMapOfC: OrderedMap = orderedMapOfB; 67 | 68 | // OrderedSet covariance 69 | declare var orderedSetOfB: OrderedSet; 70 | let orderedSetOfA: OrderedSet = orderedSetOfB; 71 | // $ExpectType OrderedSet 72 | orderedSetOfA = OrderedSet([new B()]); 73 | // $ExpectError 74 | let orderedSetOfC: OrderedSet = orderedSetOfB; 75 | -------------------------------------------------------------------------------- /__tests__/ObjectSeq.ts: -------------------------------------------------------------------------------- 1 | import { Seq } from 'immutable'; 2 | 3 | describe('ObjectSequence', () => { 4 | it('maps', () => { 5 | const i = Seq({ a: 'A', b: 'B', c: 'C' }); 6 | const m = i.map(x => x + x).toObject(); 7 | expect(m).toEqual({ a: 'AA', b: 'BB', c: 'CC' }); 8 | }); 9 | 10 | it('reduces', () => { 11 | const i = Seq({ a: 'A', b: 'B', c: 'C' }); 12 | const r = i.reduce((acc, x) => acc + x, ''); 13 | expect(r).toEqual('ABC'); 14 | }); 15 | 16 | it('extracts keys', () => { 17 | const i = Seq({ a: 'A', b: 'B', c: 'C' }); 18 | const k = i.keySeq().toArray(); 19 | expect(k).toEqual(['a', 'b', 'c']); 20 | }); 21 | 22 | it('is reversable', () => { 23 | const i = Seq({ a: 'A', b: 'B', c: 'C' }); 24 | const k = i.reverse().toArray(); 25 | expect(k).toEqual([ 26 | ['c', 'C'], 27 | ['b', 'B'], 28 | ['a', 'A'], 29 | ]); 30 | }); 31 | 32 | it('is double reversable', () => { 33 | const i = Seq({ a: 'A', b: 'B', c: 'C' }); 34 | const k = i.reverse().reverse().toArray(); 35 | expect(k).toEqual([ 36 | ['a', 'A'], 37 | ['b', 'B'], 38 | ['c', 'C'], 39 | ]); 40 | }); 41 | 42 | it('can be iterated', () => { 43 | const obj = { a: 1, b: 2, c: 3 }; 44 | const seq = Seq(obj); 45 | const entries = seq.entries(); 46 | expect(entries.next()).toEqual({ value: ['a', 1], done: false }); 47 | expect(entries.next()).toEqual({ value: ['b', 2], done: false }); 48 | expect(entries.next()).toEqual({ value: ['c', 3], done: false }); 49 | expect(entries.next()).toEqual({ value: undefined, done: true }); 50 | }); 51 | 52 | it('cannot be mutated after calling toObject', () => { 53 | const seq = Seq({ a: 1, b: 2, c: 3 }); 54 | 55 | const obj = seq.toObject(); 56 | obj.c = 10; 57 | const seq2 = Seq(obj); 58 | 59 | expect(seq.get('c')).toEqual(3); 60 | expect(seq2.get('c')).toEqual(10); 61 | }); 62 | }); 63 | -------------------------------------------------------------------------------- /src/OrderedSet.js: -------------------------------------------------------------------------------- 1 | import { SetCollection, KeyedCollection } from './Collection'; 2 | import { IS_ORDERED_SYMBOL } from './predicates/isOrdered'; 3 | import { isOrderedSet } from './predicates/isOrderedSet'; 4 | import { IndexedCollectionPrototype } from './CollectionImpl'; 5 | import { Set } from './Set'; 6 | import { emptyOrderedMap } from './OrderedMap'; 7 | import assertNotInfinite from './utils/assertNotInfinite'; 8 | 9 | export class OrderedSet extends Set { 10 | // @pragma Construction 11 | 12 | constructor(value) { 13 | return value === undefined || value === null 14 | ? emptyOrderedSet() 15 | : isOrderedSet(value) 16 | ? value 17 | : emptyOrderedSet().withMutations(set => { 18 | const iter = SetCollection(value); 19 | assertNotInfinite(iter.size); 20 | iter.forEach(v => set.add(v)); 21 | }); 22 | } 23 | 24 | static of(/*...values*/) { 25 | return this(arguments); 26 | } 27 | 28 | static fromKeys(value) { 29 | return this(KeyedCollection(value).keySeq()); 30 | } 31 | 32 | toString() { 33 | return this.__toString('OrderedSet {', '}'); 34 | } 35 | } 36 | 37 | OrderedSet.isOrderedSet = isOrderedSet; 38 | 39 | const OrderedSetPrototype = OrderedSet.prototype; 40 | OrderedSetPrototype[IS_ORDERED_SYMBOL] = true; 41 | OrderedSetPrototype.zip = IndexedCollectionPrototype.zip; 42 | OrderedSetPrototype.zipWith = IndexedCollectionPrototype.zipWith; 43 | OrderedSetPrototype.zipAll = IndexedCollectionPrototype.zipAll; 44 | 45 | OrderedSetPrototype.__empty = emptyOrderedSet; 46 | OrderedSetPrototype.__make = makeOrderedSet; 47 | 48 | function makeOrderedSet(map, ownerID) { 49 | const set = Object.create(OrderedSetPrototype); 50 | set.size = map ? map.size : 0; 51 | set._map = map; 52 | set.__ownerID = ownerID; 53 | return set; 54 | } 55 | 56 | let EMPTY_ORDERED_SET; 57 | function emptyOrderedSet() { 58 | return ( 59 | EMPTY_ORDERED_SET || (EMPTY_ORDERED_SET = makeOrderedSet(emptyOrderedMap())) 60 | ); 61 | } 62 | -------------------------------------------------------------------------------- /src/utils/deepEqual.js: -------------------------------------------------------------------------------- 1 | import { is } from '../is'; 2 | import { NOT_SET } from '../TrieUtils'; 3 | import { isCollection } from '../predicates/isCollection'; 4 | import { isKeyed } from '../predicates/isKeyed'; 5 | import { isIndexed } from '../predicates/isIndexed'; 6 | import { isAssociative } from '../predicates/isAssociative'; 7 | import { isOrdered } from '../predicates/isOrdered'; 8 | 9 | export default function deepEqual(a, b) { 10 | if (a === b) { 11 | return true; 12 | } 13 | 14 | if ( 15 | !isCollection(b) || 16 | (a.size !== undefined && b.size !== undefined && a.size !== b.size) || 17 | (a.__hash !== undefined && 18 | b.__hash !== undefined && 19 | a.__hash !== b.__hash) || 20 | isKeyed(a) !== isKeyed(b) || 21 | isIndexed(a) !== isIndexed(b) || 22 | isOrdered(a) !== isOrdered(b) 23 | ) { 24 | return false; 25 | } 26 | 27 | if (a.size === 0 && b.size === 0) { 28 | return true; 29 | } 30 | 31 | const notAssociative = !isAssociative(a); 32 | 33 | if (isOrdered(a)) { 34 | const entries = a.entries(); 35 | return ( 36 | b.every((v, k) => { 37 | const entry = entries.next().value; 38 | return entry && is(entry[1], v) && (notAssociative || is(entry[0], k)); 39 | }) && entries.next().done 40 | ); 41 | } 42 | 43 | let flipped = false; 44 | 45 | if (a.size === undefined) { 46 | if (b.size === undefined) { 47 | if (typeof a.cacheResult === 'function') { 48 | a.cacheResult(); 49 | } 50 | } else { 51 | flipped = true; 52 | const _ = a; 53 | a = b; 54 | b = _; 55 | } 56 | } 57 | 58 | let allEqual = true; 59 | const bSize = b.__iterate((v, k) => { 60 | if ( 61 | notAssociative 62 | ? !a.has(v) 63 | : flipped 64 | ? !is(v, a.get(k, NOT_SET)) 65 | : !is(a.get(k, NOT_SET), v) 66 | ) { 67 | allEqual = false; 68 | return false; 69 | } 70 | }); 71 | 72 | return allEqual && a.size === bSize; 73 | } 74 | -------------------------------------------------------------------------------- /__tests__/sort.ts: -------------------------------------------------------------------------------- 1 | import { List, OrderedMap, Range, Seq } from 'immutable'; 2 | 3 | describe('sort', () => { 4 | it('sorts a sequence', () => { 5 | expect(Seq([4, 5, 6, 3, 2, 1]).sort().toArray()).toEqual([ 6 | 1, 2, 3, 4, 5, 6, 7 | ]); 8 | }); 9 | 10 | it('sorts a list', () => { 11 | expect(List([4, 5, 6, 3, 2, 1]).sort().toArray()).toEqual([ 12 | 1, 2, 3, 4, 5, 6, 13 | ]); 14 | }); 15 | 16 | it('sorts undefined values last', () => { 17 | expect( 18 | List([4, undefined, 5, 6, 3, undefined, 2, 1]).sort().toArray() 19 | ).toEqual([1, 2, 3, 4, 5, 6, undefined, undefined]); 20 | }); 21 | 22 | it('sorts a keyed sequence', () => { 23 | expect( 24 | Seq({ z: 1, y: 2, x: 3, c: 3, b: 2, a: 1 }).sort().entrySeq().toArray() 25 | ).toEqual([ 26 | ['z', 1], 27 | ['a', 1], 28 | ['y', 2], 29 | ['b', 2], 30 | ['x', 3], 31 | ['c', 3], 32 | ]); 33 | }); 34 | 35 | it('sorts an OrderedMap', () => { 36 | expect( 37 | OrderedMap({ z: 1, y: 2, x: 3, c: 3, b: 2, a: 1 }) 38 | .sort() 39 | .entrySeq() 40 | .toArray() 41 | ).toEqual([ 42 | ['z', 1], 43 | ['a', 1], 44 | ['y', 2], 45 | ['b', 2], 46 | ['x', 3], 47 | ['c', 3], 48 | ]); 49 | }); 50 | 51 | it('accepts a sort function', () => { 52 | expect( 53 | Seq([4, 5, 6, 3, 2, 1]) 54 | .sort((a, b) => b - a) 55 | .toArray() 56 | ).toEqual([6, 5, 4, 3, 2, 1]); 57 | }); 58 | 59 | it('sorts by using a mapper', () => { 60 | expect( 61 | Range(1, 10) 62 | .sortBy(v => v % 3) 63 | .toArray() 64 | ).toEqual([3, 6, 9, 1, 4, 7, 2, 5, 8]); 65 | }); 66 | 67 | it('sorts by using a mapper and a sort function', () => { 68 | expect( 69 | Range(1, 10) 70 | .sortBy( 71 | v => v % 3, 72 | (a: number, b: number) => b - a 73 | ) 74 | .toArray() 75 | ).toEqual([2, 5, 8, 1, 4, 7, 3, 6, 9]); 76 | }); 77 | }); 78 | -------------------------------------------------------------------------------- /__tests__/RecordJS.js: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line import/no-unresolved -- immutable is resolve by jest resolver 2 | import { Record } from 'immutable'; 3 | 4 | describe('Record', () => { 5 | it('defines a record factory', () => { 6 | const MyType = Record({ a: 1, b: 2, c: 3 }); 7 | 8 | const t = MyType(); 9 | const t2 = t.set('a', 10); 10 | 11 | expect(t.a).toBe(1); 12 | expect(t2.a).toBe(10); 13 | }); 14 | 15 | it('can have mutations apply', () => { 16 | const MyType = Record({ a: 1, b: 2, c: 3 }); 17 | 18 | const t = MyType(); 19 | 20 | expect(() => { 21 | t.a = 10; 22 | }).toThrow(); 23 | 24 | const t2 = t.withMutations(mt => { 25 | mt.a = 10; 26 | mt.b = 20; 27 | mt.c = 30; 28 | }); 29 | 30 | expect(t.a).toBe(1); 31 | expect(t2.a).toBe(10); 32 | }); 33 | 34 | it('can be subclassed', () => { 35 | class Alphabet extends Record({ a: 1, b: 2, c: 3 }) { 36 | soup() { 37 | return this.a + this.b + this.c; 38 | } 39 | } 40 | 41 | // Note: `new` is only used because of `class` 42 | const t = new Alphabet(); 43 | const t2 = t.set('b', 200); 44 | 45 | expect(t instanceof Record); 46 | expect(t instanceof Alphabet); 47 | expect(t.soup()).toBe(6); 48 | expect(t2.soup()).toBe(204); 49 | 50 | // Uses class name as descriptive name 51 | expect(Record.getDescriptiveName(t)).toBe('Alphabet'); 52 | 53 | // Uses display name over class name 54 | class NotADisplayName extends Record({ x: 1 }, 'DisplayName') {} 55 | const t3 = new NotADisplayName(); 56 | expect(Record.getDescriptiveName(t3)).toBe('DisplayName'); 57 | }); 58 | 59 | it('can be cleared', () => { 60 | const MyType = Record({ a: 1, b: 2, c: 3 }); 61 | let t = MyType({ c: 'cats' }); 62 | 63 | expect(t.c).toBe('cats'); 64 | t = t.clear(); 65 | expect(t.c).toBe(3); 66 | 67 | const MyType2 = Record({ d: 4, e: 5, f: 6 }); 68 | let t2 = MyType2({ d: 'dogs' }); 69 | 70 | expect(t2.d).toBe('dogs'); 71 | t2 = t2.clear(); 72 | expect(t2.d).toBe(4); 73 | }); 74 | }); 75 | -------------------------------------------------------------------------------- /perf/Record.js: -------------------------------------------------------------------------------- 1 | describe('Record', () => { 2 | describe('builds from an object', () => { 3 | [2, 5, 10, 100, 1000].forEach(size => { 4 | var defaults = {}; 5 | var values = {}; 6 | for (var ii = 0; ii < size; ii++) { 7 | defaults['x' + ii] = null; 8 | values['x' + ii] = ii; 9 | } 10 | 11 | var Rec = Immutable.Record(defaults); 12 | 13 | it('of ' + size, () => { 14 | Rec(values); 15 | }); 16 | }); 17 | }); 18 | 19 | describe('update random using set()', () => { 20 | [2, 5, 10, 100, 1000].forEach(size => { 21 | var defaults = {}; 22 | var values = {}; 23 | for (var ii = 0; ii < size; ii++) { 24 | defaults['x' + ii] = null; 25 | values['x' + ii] = ii; 26 | } 27 | 28 | var Rec = Immutable.Record(defaults); 29 | var rec = Rec(values); 30 | 31 | var key = 'x' + Math.floor(size / 2); 32 | 33 | it('of ' + size, () => { 34 | rec.set(key, 999); 35 | }); 36 | }); 37 | }); 38 | 39 | describe('access random using get()', () => { 40 | [2, 5, 10, 100, 1000].forEach(size => { 41 | var defaults = {}; 42 | var values = {}; 43 | for (var ii = 0; ii < size; ii++) { 44 | defaults['x' + ii] = null; 45 | values['x' + ii] = ii; 46 | } 47 | 48 | var Rec = Immutable.Record(defaults); 49 | var rec = Rec(values); 50 | 51 | var key = 'x' + Math.floor(size / 2); 52 | 53 | it('of ' + size, () => { 54 | rec.get(key); 55 | }); 56 | }); 57 | }); 58 | 59 | describe('access random using property', () => { 60 | [2, 5, 10, 100, 1000].forEach(size => { 61 | var defaults = {}; 62 | var values = {}; 63 | for (var ii = 0; ii < size; ii++) { 64 | defaults['x' + ii] = null; 65 | values['x' + ii] = ii; 66 | } 67 | 68 | var Rec = Immutable.Record(defaults); 69 | var rec = Rec(values); 70 | 71 | var key = 'x' + Math.floor(size / 2); 72 | 73 | it('of ' + size, () => { 74 | rec[key]; 75 | }); 76 | }); 77 | }); 78 | }); 79 | -------------------------------------------------------------------------------- /__tests__/utils.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @jest-environment jsdom 3 | */ 4 | /* global document */ 5 | // eslint-disable-next-line import/no-unresolved -- immutable is resolve by jest resolver 6 | import { List, isPlainObject } from 'immutable'; 7 | 8 | describe('Utils', () => { 9 | describe('isPlainObj()', function testFunc() { 10 | const nonPlainCases = [ 11 | ['Host object', document.createElement('div')], 12 | ['bool primitive false', false], 13 | ['bool primitive true', true], 14 | ['falsy undefined', undefined], 15 | ['falsy null', null], 16 | ['Simple function', function () {}], 17 | [ 18 | 'Instance of other object', 19 | (function () { 20 | function Foo() {} 21 | return new Foo(); 22 | })(), 23 | ], 24 | ['Number primitive ', 5], 25 | ['String primitive ', 'P'], 26 | ['Number Object', Number(6)], 27 | ['Immutable.List', new List()], 28 | ['simple array', ['one']], 29 | ['Error', Error], 30 | ['Internal namespaces', Math], 31 | ['Arguments', arguments], 32 | ]; 33 | const plainCases = [ 34 | ['literal Object', {}], 35 | ['new Object', new Object()], // eslint-disable-line no-new-object 36 | ['Object.create(null)', Object.create(null)], 37 | ['nested object', { one: { prop: 'two' } }], 38 | ['constructor prop', { constructor: 'prop' }], // shadows an object's constructor 39 | ['constructor.name', { constructor: { name: 'two' } }], // shadows an object's constructor.name 40 | [ 41 | 'Fake toString', 42 | { 43 | toString: function () { 44 | return '[object Object]'; 45 | }, 46 | }, 47 | ], 48 | ]; 49 | 50 | nonPlainCases.forEach(([name, value]) => { 51 | it(`${name} returns false`, () => { 52 | expect(isPlainObject(value)).toBe(false); 53 | }); 54 | }); 55 | 56 | plainCases.forEach(([name, value]) => { 57 | it(`${name} returns true`, () => { 58 | expect(isPlainObject(value)).toBe(true); 59 | }); 60 | }); 61 | }); 62 | }); 63 | -------------------------------------------------------------------------------- /src/functional/updateIn.js: -------------------------------------------------------------------------------- 1 | import { isImmutable } from '../predicates/isImmutable'; 2 | import coerceKeyPath from '../utils/coerceKeyPath'; 3 | import isDataStructure from '../utils/isDataStructure'; 4 | import quoteString from '../utils/quoteString'; 5 | import { NOT_SET } from '../TrieUtils'; 6 | import { emptyMap } from '../Map'; 7 | import { get } from './get'; 8 | import { remove } from './remove'; 9 | import { set } from './set'; 10 | 11 | export function updateIn(collection, keyPath, notSetValue, updater) { 12 | if (!updater) { 13 | updater = notSetValue; 14 | notSetValue = undefined; 15 | } 16 | const updatedValue = updateInDeeply( 17 | isImmutable(collection), 18 | collection, 19 | coerceKeyPath(keyPath), 20 | 0, 21 | notSetValue, 22 | updater 23 | ); 24 | return updatedValue === NOT_SET ? notSetValue : updatedValue; 25 | } 26 | 27 | function updateInDeeply( 28 | inImmutable, 29 | existing, 30 | keyPath, 31 | i, 32 | notSetValue, 33 | updater 34 | ) { 35 | const wasNotSet = existing === NOT_SET; 36 | if (i === keyPath.length) { 37 | const existingValue = wasNotSet ? notSetValue : existing; 38 | const newValue = updater(existingValue); 39 | return newValue === existingValue ? existing : newValue; 40 | } 41 | if (!wasNotSet && !isDataStructure(existing)) { 42 | throw new TypeError( 43 | 'Cannot update within non-data-structure value in path [' + 44 | keyPath.slice(0, i).map(quoteString) + 45 | ']: ' + 46 | existing 47 | ); 48 | } 49 | const key = keyPath[i]; 50 | const nextExisting = wasNotSet ? NOT_SET : get(existing, key, NOT_SET); 51 | const nextUpdated = updateInDeeply( 52 | nextExisting === NOT_SET ? inImmutable : isImmutable(nextExisting), 53 | nextExisting, 54 | keyPath, 55 | i + 1, 56 | notSetValue, 57 | updater 58 | ); 59 | return nextUpdated === nextExisting 60 | ? existing 61 | : nextUpdated === NOT_SET 62 | ? remove(existing, key) 63 | : set( 64 | wasNotSet ? (inImmutable ? emptyMap() : {}) : existing, 65 | key, 66 | nextUpdated 67 | ); 68 | } 69 | -------------------------------------------------------------------------------- /type-definitions/ts-tests/record.ts: -------------------------------------------------------------------------------- 1 | import { List, Map, Record, Set } from 'immutable'; 2 | 3 | { 4 | // Factory 5 | const PointXY = Record({ x: 0, y: 0 }); 6 | 7 | // $ExpectType Factory<{ x: number; y: number; }> 8 | PointXY; 9 | 10 | // $ExpectError 11 | PointXY({ x: 'a' }); 12 | 13 | const pointXY = PointXY(); 14 | 15 | // $ExpectType Record<{ x: number; y: number; }> & Readonly<{ x: number; y: number; }> 16 | pointXY; 17 | 18 | // $ExpectType number 19 | pointXY.x; 20 | 21 | // $ExpectError 22 | pointXY.x = 10; 23 | 24 | // $ExpectType number 25 | pointXY.y; 26 | 27 | // $ExpectError 28 | pointXY.y = 10; 29 | 30 | // $ExpectType { x: number; y: number; } 31 | pointXY.toJS(); 32 | 33 | class PointClass extends PointXY { 34 | setX(x: number) { 35 | return this.set('x', x); 36 | } 37 | 38 | setY(y: number) { 39 | return this.set('y', y); 40 | } 41 | } 42 | 43 | const point = new PointClass(); 44 | 45 | // $ExpectType PointClass 46 | point; 47 | 48 | // $ExpectType number 49 | point.x; 50 | 51 | // $ExpectType number 52 | point.y; 53 | 54 | // $ExpectType PointClass 55 | point.setX(10); 56 | 57 | // $ExpectType PointClass 58 | point.setY(10); 59 | 60 | // $ExpectType { x: number; y: number; } 61 | point.toJSON(); 62 | 63 | // $ExpectType { x: number; y: number; } 64 | point.toJS(); 65 | } 66 | 67 | { 68 | // .getDescriptiveName 69 | const PointXY = Record({ x: 0, y: 0 }); 70 | 71 | // $ExpectType string 72 | Record.getDescriptiveName(PointXY()); 73 | 74 | // $ExpectError 75 | Record.getDescriptiveName({}); 76 | } 77 | 78 | { 79 | // Factory 80 | const WithMap = Record({ 81 | map: Map({ a: 'A' }), 82 | list: List(['a']), 83 | set: Set(['a']), 84 | }); 85 | 86 | const withMap = WithMap(); 87 | 88 | // $ExpectType { map: Map; list: List; set: Set; } 89 | withMap.toJSON(); 90 | 91 | // should be `{ map: { [x: string]: string; }; list: string[]; set: string[]; }` but there is an issue with circular references 92 | // $ExpectType { map: unknown; list: unknown; set: unknown; } 93 | withMap.toJS(); 94 | } 95 | -------------------------------------------------------------------------------- /__tests__/splice.ts: -------------------------------------------------------------------------------- 1 | import { List, Range, Seq } from 'immutable'; 2 | 3 | import * as jasmineCheck from 'jasmine-check'; 4 | jasmineCheck.install(); 5 | 6 | describe('splice', () => { 7 | it('splices a sequence only removing elements', () => { 8 | expect(Seq([1, 2, 3]).splice(0, 1).toArray()).toEqual([2, 3]); 9 | expect(Seq([1, 2, 3]).splice(1, 1).toArray()).toEqual([1, 3]); 10 | expect(Seq([1, 2, 3]).splice(2, 1).toArray()).toEqual([1, 2]); 11 | expect(Seq([1, 2, 3]).splice(3, 1).toArray()).toEqual([1, 2, 3]); 12 | }); 13 | 14 | it('splices a list only removing elements', () => { 15 | expect(List([1, 2, 3]).splice(0, 1).toArray()).toEqual([2, 3]); 16 | expect(List([1, 2, 3]).splice(1, 1).toArray()).toEqual([1, 3]); 17 | expect(List([1, 2, 3]).splice(2, 1).toArray()).toEqual([1, 2]); 18 | expect(List([1, 2, 3]).splice(3, 1).toArray()).toEqual([1, 2, 3]); 19 | }); 20 | 21 | it('splicing by infinity', () => { 22 | const l = List(['a', 'b', 'c', 'd']); 23 | expect(l.splice(2, Infinity, 'x').toArray()).toEqual(['a', 'b', 'x']); 24 | expect(l.splice(Infinity, 2, 'x').toArray()).toEqual([ 25 | 'a', 26 | 'b', 27 | 'c', 28 | 'd', 29 | 'x', 30 | ]); 31 | 32 | const s = List(['a', 'b', 'c', 'd']); 33 | expect(s.splice(2, Infinity, 'x').toArray()).toEqual(['a', 'b', 'x']); 34 | expect(s.splice(Infinity, 2, 'x').toArray()).toEqual([ 35 | 'a', 36 | 'b', 37 | 'c', 38 | 'd', 39 | 'x', 40 | ]); 41 | }); 42 | 43 | it('has the same behavior as array splice in known edge cases', () => { 44 | // arbitrary numbers that sum to 31 45 | const a = Range(0, 49).toArray(); 46 | const v = List(a); 47 | a.splice(-18, 0, 0); 48 | expect(v.splice(-18, 0, 0).toList().toArray()).toEqual(a); 49 | }); 50 | 51 | check.it( 52 | 'has the same behavior as array splice', 53 | [gen.array(gen.int), gen.array(gen.oneOf([gen.int, gen.undefined]))], 54 | (values, args) => { 55 | const v = List(values); 56 | const a = values.slice(); // clone 57 | const splicedV = v.splice.apply(v, args); // persistent 58 | a.splice.apply(a, args); // mutative 59 | expect(splicedV.toArray()).toEqual(a); 60 | } 61 | ); 62 | }); 63 | -------------------------------------------------------------------------------- /website/src/pages/docs/[version]/[type].tsx: -------------------------------------------------------------------------------- 1 | import Head from 'next/head'; 2 | 3 | import { DocHeader } from '../../../DocHeader'; 4 | import { ImmutableConsole } from '../../../ImmutableConsole'; 5 | import { getSidebarLinks, SidebarLinks } from '../../../Sidebar'; 6 | import { getTypeDefs } from '../../../static/getTypeDefs'; 7 | import { getVersions } from '../../../static/getVersions'; 8 | import type { TypeDefinition } from '../../../TypeDefs'; 9 | import { TypeDocumentation } from '../../../TypeDocumentation'; 10 | 11 | type Params = { 12 | version: string; 13 | type: string; 14 | }; 15 | 16 | type Props = { 17 | versions: Array; 18 | version: string; 19 | def: TypeDefinition; 20 | sidebarLinks: SidebarLinks; 21 | }; 22 | 23 | export async function getStaticProps(context: { 24 | params: Params; 25 | }): Promise<{ props: Props }> { 26 | const versions = getVersions(); 27 | const { version, type } = context.params; 28 | const defs = getTypeDefs(version); 29 | const def = Object.values(defs.types).find(d => d.label === type); 30 | if (!def) { 31 | throw new Error('404'); 32 | } 33 | return { 34 | props: { versions, version, def, sidebarLinks: getSidebarLinks(defs) }, 35 | }; 36 | } 37 | 38 | export function getStaticPaths(): { 39 | paths: Array<{ params: Params }>; 40 | fallback: boolean; 41 | } { 42 | return { 43 | paths: getVersions() 44 | .map(version => 45 | Object.values(getTypeDefs(version).types).map(def => ({ 46 | params: { version, type: def.label }, 47 | })) 48 | ) 49 | .flat(), 50 | fallback: false, 51 | }; 52 | } 53 | 54 | export default function TypeDocPage({ 55 | versions, 56 | version, 57 | def, 58 | sidebarLinks, 59 | }: Props) { 60 | return ( 61 |
62 | 63 | {def.qualifiedName} — Immutable.js 64 | 65 | 66 | 67 | 68 |
69 |
70 | 71 |
72 |
73 |
74 | ); 75 | } 76 | -------------------------------------------------------------------------------- /website/src/pages/docs/[version]/index.tsx: -------------------------------------------------------------------------------- 1 | import Head from 'next/head'; 2 | import { getVersions } from '../../../static/getVersions'; 3 | import { getTypeDefs } from '../../../static/getTypeDefs'; 4 | import { DocHeader } from '../../../DocHeader'; 5 | import { 6 | DocOverview, 7 | getOverviewData, 8 | OverviewData, 9 | } from '../../../DocOverview'; 10 | import { DocSearch } from '../../../DocSearch'; 11 | import { ImmutableConsole } from '../../../ImmutableConsole'; 12 | import { SideBar, getSidebarLinks, SidebarLinks } from '../../../Sidebar'; 13 | 14 | type Params = { 15 | version: string; 16 | }; 17 | 18 | type Props = { 19 | versions: Array; 20 | version: string; 21 | overviewData: OverviewData; 22 | sidebarLinks: SidebarLinks; 23 | }; 24 | 25 | export async function getStaticProps(context: { 26 | params: Params; 27 | }): Promise<{ props: Props }> { 28 | const versions = getVersions(); 29 | const { version } = context.params; 30 | const defs = getTypeDefs(version); 31 | const overviewData = getOverviewData(defs); 32 | const sidebarLinks = getSidebarLinks(defs); 33 | 34 | return { 35 | props: { 36 | versions, 37 | version, 38 | overviewData, 39 | sidebarLinks, 40 | }, 41 | }; 42 | } 43 | 44 | export function getStaticPaths(): { 45 | paths: Array<{ params: Params }>; 46 | fallback: boolean; 47 | } { 48 | return { 49 | paths: [...getVersions().map(version => ({ params: { version } }))], 50 | fallback: false, 51 | }; 52 | } 53 | 54 | export default function OverviewDocPage({ 55 | versions, 56 | version, 57 | overviewData, 58 | sidebarLinks, 59 | }: Props) { 60 | return ( 61 |
62 | 63 | Documentation {version} — Immutable.js 64 | 65 | 66 | 67 |
68 |
69 | 70 |
71 | 72 |

Immutable.js ({version})

73 | 74 |
75 |
76 |
77 |
78 | ); 79 | } 80 | -------------------------------------------------------------------------------- /__tests__/fromJS.ts: -------------------------------------------------------------------------------- 1 | import { runInNewContext } from 'vm'; 2 | 3 | import { List, Map, Set, isCollection, fromJS } from 'immutable'; 4 | 5 | describe('fromJS', () => { 6 | it('convert Array to Immutable.List', () => { 7 | const list = fromJS([1, 2, 3]); 8 | expect(List.isList(list)).toBe(true); 9 | expect(list.count()).toBe(3); 10 | }); 11 | 12 | it('convert plain Object to Immutable.Map', () => { 13 | const map = fromJS({ a: 'A', b: 'B', c: 'C' }); 14 | expect(Map.isMap(map)).toBe(true); 15 | expect(map.count()).toBe(3); 16 | }); 17 | 18 | it('convert JS (global) Set to Immutable.Set', () => { 19 | const set = fromJS(new global.Set([1, 2, 3])); 20 | expect(Set.isSet(set)).toBe(true); 21 | expect(set.count()).toBe(3); 22 | }); 23 | 24 | it('convert JS (global) Map to Immutable.Map', () => { 25 | const map = fromJS( 26 | new global.Map([ 27 | ['a', 'A'], 28 | ['b', 'B'], 29 | ['c', 'C'], 30 | ]) 31 | ); 32 | expect(Map.isMap(map)).toBe(true); 33 | expect(map.count()).toBe(3); 34 | }); 35 | 36 | it('convert iterable to Immutable collection', () => { 37 | function* values() { 38 | yield 1; 39 | yield 2; 40 | yield 3; 41 | } 42 | const result = fromJS(values()); 43 | expect(List.isList(result)).toBe(true); 44 | expect(result.count()).toBe(3); 45 | }); 46 | 47 | it('does not convert existing Immutable collections', () => { 48 | const orderedSet = Set(['a', 'b', 'c']); 49 | expect(fromJS(orderedSet)).toBe(orderedSet); 50 | }); 51 | 52 | it('does not convert strings', () => { 53 | expect(fromJS('abc')).toBe('abc'); 54 | }); 55 | 56 | it('does not convert non-plain Objects', () => { 57 | class Test {} 58 | const result = fromJS(new Test()); 59 | expect(isCollection(result)).toBe(false); 60 | expect(result instanceof Test).toBe(true); 61 | }); 62 | 63 | it('is iterable outside of a vm', () => { 64 | expect(isCollection(fromJS({}))).toBe(true); 65 | }); 66 | 67 | it('is iterable inside of a vm', () => { 68 | runInNewContext( 69 | ` 70 | expect(isCollection(fromJS({}))).toBe(true); 71 | `, 72 | { 73 | expect, 74 | isCollection, 75 | fromJS, 76 | }, 77 | {} 78 | ); 79 | }); 80 | }); 81 | -------------------------------------------------------------------------------- /__tests__/Comparator.ts: -------------------------------------------------------------------------------- 1 | import { List, OrderedSet, Seq, Comparator, PairSorting } from 'immutable'; 2 | 3 | const sourceNumbers: readonly number[] = [3, 4, 5, 6, 7, 9, 10, 12, 90, 92, 95]; 4 | 5 | const expectedSortedNumbers: readonly number[] = [ 6 | 7, 95, 90, 92, 3, 5, 9, 4, 6, 10, 12, 7 | ]; 8 | 9 | const testComparator: Comparator = (left, right) => { 10 | //The number 7 always goes first... 11 | if (left == 7) { 12 | return PairSorting.LeftThenRight; 13 | } else if (right == 7) { 14 | return PairSorting.RightThenLeft; 15 | } 16 | 17 | //...followed by numbers >= 90, then by all the others. 18 | if (left >= 90 && right < 90) { 19 | return PairSorting.LeftThenRight; 20 | } else if (left < 90 && right >= 90) { 21 | return PairSorting.RightThenLeft; 22 | } 23 | 24 | //Within each group, even numbers go first... 25 | if (left % 2 && !(right % 2)) { 26 | return PairSorting.LeftThenRight; 27 | } else if (!(left % 2) && right % 2) { 28 | return PairSorting.RightThenLeft; 29 | } 30 | 31 | //...and, finally, sort the numbers of each subgroup in ascending order. 32 | return left - right; 33 | }; 34 | 35 | describe.each([ 36 | ['List', List], 37 | ['OrderedSet', OrderedSet], 38 | ['Seq.Indexed', Seq.Indexed], 39 | ])('Comparator applied to %s', (_collectionName, testCollectionConstructor) => { 40 | const sourceCollection = testCollectionConstructor(sourceNumbers); 41 | 42 | const expectedSortedCollection = testCollectionConstructor( 43 | expectedSortedNumbers 44 | ); 45 | 46 | describe('when sorting', () => { 47 | it('should support the enum as well as numeric return values', () => { 48 | const actualCollection = sourceCollection.sort(testComparator); 49 | expect(actualCollection).toEqual(expectedSortedCollection); 50 | }); 51 | }); 52 | 53 | describe('when retrieving the max value', () => { 54 | it('should support the enum as well as numeric return values', () => { 55 | const actualMax = sourceCollection.max(testComparator); 56 | expect(actualMax).toBe(12); 57 | }); 58 | }); 59 | 60 | describe('when retrieving the min value', () => { 61 | it('should support the enum as well as numeric return values', () => { 62 | const actualMin = sourceCollection.min(testComparator); 63 | expect(actualMin).toBe(7); 64 | }); 65 | }); 66 | }); 67 | -------------------------------------------------------------------------------- /resources/jestPreprocessor.js: -------------------------------------------------------------------------------- 1 | var typescript = require('typescript'); 2 | const makeSynchronous = require('make-synchronous'); 3 | 4 | const TYPESCRIPT_OPTIONS = { 5 | noEmitOnError: true, 6 | target: typescript.ScriptTarget.ES2015, 7 | module: typescript.ModuleKind.CommonJS, 8 | strictNullChecks: true, 9 | sourceMap: true, 10 | inlineSourceMap: true, 11 | }; 12 | 13 | function transpileTypeScript(src, path) { 14 | return typescript.transpile(src, TYPESCRIPT_OPTIONS, path, []); 15 | } 16 | 17 | function transpileJavaScript(src, path) { 18 | // Need to make this sync by calling `makeSynchronous` 19 | // while https://github.com/facebook/jest/issues/9504 is not resolved 20 | const fn = makeSynchronous(async path => { 21 | const rollup = require('rollup'); 22 | const buble = require('rollup-plugin-buble'); 23 | const commonjs = require('rollup-plugin-commonjs'); 24 | const json = require('rollup-plugin-json'); 25 | const stripBanner = require('rollup-plugin-strip-banner'); 26 | 27 | // same input options as in rollup-config.js 28 | const inputOptions = { 29 | input: path, 30 | onwarn: () => {}, 31 | plugins: [commonjs(), json(), stripBanner(), buble()], 32 | }; 33 | 34 | const bundle = await rollup.rollup(inputOptions); 35 | 36 | const { output } = await bundle.generate({ 37 | file: path, 38 | format: 'cjs', 39 | sourcemap: true, 40 | }); 41 | 42 | await bundle.close(); 43 | 44 | const { code, map } = output[0]; 45 | 46 | if (!code) { 47 | throw new Error( 48 | 'Unable to get code from rollup output in jestPreprocessor. Did rollup version changed ?' 49 | ); 50 | } 51 | 52 | return { code, map }; 53 | }); 54 | 55 | return fn(path); 56 | } 57 | 58 | module.exports = { 59 | process(src, path) { 60 | if (path.endsWith('__tests__/MultiRequire.js')) { 61 | // exit early for multi-require as we explicitly want to have several instances 62 | return src; 63 | } 64 | 65 | if (path.endsWith('.ts') || path.endsWith('.tsx')) { 66 | return transpileTypeScript(src, path); 67 | } 68 | 69 | return transpileJavaScript(src, path); 70 | }, 71 | 72 | getCacheKey() { 73 | // ignore cache, as there is a conflict between rollup compile and jest preprocessor. 74 | return Date.now().toString(); 75 | }, 76 | }; 77 | -------------------------------------------------------------------------------- /type-definitions/ts-tests/exports.ts: -------------------------------------------------------------------------------- 1 | // Some tests look like they are repeated in order to avoid false positives. 2 | 3 | import * as Immutable from 'immutable'; 4 | import { 5 | List, 6 | Map, 7 | OrderedMap, 8 | OrderedSet, 9 | Range, 10 | Repeat, 11 | Seq, 12 | Set, 13 | Stack, 14 | Collection, 15 | } from 'immutable'; 16 | 17 | List; // $ExpectType typeof List 18 | Map; // $ExpectType typeof Map 19 | OrderedMap; // $ExpectType typeof OrderedMap 20 | OrderedSet; // $ExpectType typeof OrderedSet 21 | // TODO: Turn on once https://github.com/Microsoft/dtslint/issues/19 is resolved. 22 | Range; // $ ExpectType (start?: number | undefined, end?: number | undefined, step?: number | undefined) => Indexed 23 | Repeat; // $ExpectType (value: T, times?: number | undefined) => Indexed 24 | Seq; // $ExpectType typeof Seq 25 | Set; // $ExpectType typeof Set 26 | Stack; // $ExpectType typeof Stack 27 | Collection; // $ExpectType typeof Collection 28 | Collection.Set; // $ExpectType (collection?: Iterable | ArrayLike | undefined) => Set 29 | Collection.Keyed; // $ ExpectType { (collection: Iterable<[K, V]>): Keyed; (obj: { [key: string]: V; }): Keyed } 30 | Collection.Indexed; // $ExpectType (collection?: Iterable | ArrayLike | undefined) => Indexed 31 | 32 | Immutable.List; // $ExpectType typeof List 33 | Immutable.Map; // $ExpectType typeof Map 34 | Immutable.OrderedMap; // $ExpectType typeof OrderedMap 35 | Immutable.OrderedSet; // $ExpectType typeof OrderedSet 36 | // TODO: Turn on once https://github.com/Microsoft/dtslint/issues/19 is resolved. 37 | Immutable.Range; // $ ExpectType (start?: number | undefined, end?: number | undefined, step?: number | undefined) => Indexed 38 | Immutable.Repeat; // $ExpectType (value: T, times?: number | undefined) => Indexed 39 | Immutable.Seq; // $ExpectType typeof Seq 40 | Immutable.Set; // $ExpectType typeof Set 41 | Immutable.Stack; // $ExpectType typeof Stack 42 | Immutable.Collection; // $ExpectType typeof Collection 43 | Immutable.Collection.Set; // $ExpectType (collection?: Iterable | ArrayLike | undefined) => Set 44 | Immutable.Collection.Keyed; // $ ExpectType { (collection: Iterable<[K, V]>): Keyed; (obj: { [key: string]: V; }): Keyed } 45 | Immutable.Collection.Indexed; // $ExpectType (collection?: Iterable | ArrayLike | undefined) => Indexed 46 | -------------------------------------------------------------------------------- /resources/dist-stats.js: -------------------------------------------------------------------------------- 1 | const { exec } = require('child_process'); 2 | const { deflate } = require('zlib'); 3 | const fs = require('fs'); 4 | 5 | require('colors'); 6 | 7 | const fileContent = filePath => 8 | new Promise((resolve, reject) => 9 | fs.readFile(filePath, (error, out) => 10 | error ? reject(error) : resolve(out) 11 | ) 12 | ); 13 | 14 | const gitContent = gitPath => 15 | new Promise(resolve => 16 | exec(`git show ${gitPath}`, (error, out) => { 17 | if (error) { 18 | console.log( 19 | `"git show ${gitPath}" failed, resulting in an empty diff.`.yellow 20 | ); 21 | resolve(''); 22 | return; 23 | } 24 | resolve(out); 25 | }) 26 | ); 27 | 28 | const deflateContent = content => 29 | new Promise((resolve, reject) => 30 | deflate(content, (error, out) => (error ? reject(error) : resolve(out))) 31 | ); 32 | 33 | const space = (n, s) => 34 | new Array(Math.max(0, 10 + n - (s || '').length)).join(' ') + (s || ''); 35 | 36 | const bytes = b => 37 | `${b.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',')} bytes`; 38 | 39 | const diff = (n, o) => { 40 | const d = n - o; 41 | return d === 0 ? '' : d < 0 ? ` ${bytes(d)}`.green : ` +${bytes(d)}`.red; 42 | }; 43 | 44 | const pct = (s, b) => ` ${Math.floor(10000 * (1 - s / b)) / 100}%`.grey; 45 | 46 | Promise.all([ 47 | fileContent('dist/immutable.js'), 48 | gitContent('main:dist/immutable.js'), 49 | fileContent('dist/immutable.min.js'), 50 | gitContent('main:dist/immutable.min.js'), 51 | fileContent('dist/immutable.min.js').then(deflateContent), 52 | gitContent('main:dist/immutable.min.js').then(deflateContent), 53 | ]) 54 | .then(results => results.map(result => Buffer.byteLength(result, 'utf8'))) 55 | .then(results => results.map(result => parseInt(result, 10))) 56 | .then(([rawNew, rawOld, minNew, minOld, zipNew, zipOld]) => { 57 | console.log( 58 | ` Raw: ${space(14, bytes(rawNew).cyan)} ${space( 59 | 15, 60 | diff(rawNew, rawOld) 61 | )}` 62 | ); 63 | console.log( 64 | ` Min: ${space(14, bytes(minNew).cyan)}${pct(minNew, rawNew)}${space( 65 | 15, 66 | diff(minNew, minOld) 67 | )}` 68 | ); 69 | console.log( 70 | ` Zip: ${space(14, bytes(zipNew).cyan)}${pct(zipNew, rawNew)}${space( 71 | 15, 72 | diff(zipNew, zipOld) 73 | )}` 74 | ); 75 | }); 76 | -------------------------------------------------------------------------------- /src/Iterator.js: -------------------------------------------------------------------------------- 1 | export const ITERATE_KEYS = 0; 2 | export const ITERATE_VALUES = 1; 3 | export const ITERATE_ENTRIES = 2; 4 | 5 | const REAL_ITERATOR_SYMBOL = typeof Symbol === 'function' && Symbol.iterator; 6 | const FAUX_ITERATOR_SYMBOL = '@@iterator'; 7 | 8 | export const ITERATOR_SYMBOL = REAL_ITERATOR_SYMBOL || FAUX_ITERATOR_SYMBOL; 9 | 10 | export class Iterator { 11 | constructor(next) { 12 | this.next = next; 13 | } 14 | 15 | toString() { 16 | return '[Iterator]'; 17 | } 18 | } 19 | 20 | Iterator.KEYS = ITERATE_KEYS; 21 | Iterator.VALUES = ITERATE_VALUES; 22 | Iterator.ENTRIES = ITERATE_ENTRIES; 23 | 24 | Iterator.prototype.inspect = Iterator.prototype.toSource = function () { 25 | return this.toString(); 26 | }; 27 | Iterator.prototype[ITERATOR_SYMBOL] = function () { 28 | return this; 29 | }; 30 | 31 | export function iteratorValue(type, k, v, iteratorResult) { 32 | const value = type === 0 ? k : type === 1 ? v : [k, v]; 33 | iteratorResult 34 | ? (iteratorResult.value = value) 35 | : (iteratorResult = { 36 | value: value, 37 | done: false, 38 | }); 39 | return iteratorResult; 40 | } 41 | 42 | export function iteratorDone() { 43 | return { value: undefined, done: true }; 44 | } 45 | 46 | export function hasIterator(maybeIterable) { 47 | if (Array.isArray(maybeIterable)) { 48 | // IE11 trick as it does not support `Symbol.iterator` 49 | return true; 50 | } 51 | 52 | return !!getIteratorFn(maybeIterable); 53 | } 54 | 55 | export function isIterator(maybeIterator) { 56 | return maybeIterator && typeof maybeIterator.next === 'function'; 57 | } 58 | 59 | export function getIterator(iterable) { 60 | const iteratorFn = getIteratorFn(iterable); 61 | return iteratorFn && iteratorFn.call(iterable); 62 | } 63 | 64 | function getIteratorFn(iterable) { 65 | const iteratorFn = 66 | iterable && 67 | ((REAL_ITERATOR_SYMBOL && iterable[REAL_ITERATOR_SYMBOL]) || 68 | iterable[FAUX_ITERATOR_SYMBOL]); 69 | if (typeof iteratorFn === 'function') { 70 | return iteratorFn; 71 | } 72 | } 73 | 74 | export function isEntriesIterable(maybeIterable) { 75 | const iteratorFn = getIteratorFn(maybeIterable); 76 | return iteratorFn && iteratorFn === maybeIterable.entries; 77 | } 78 | 79 | export function isKeysIterable(maybeIterable) { 80 | const iteratorFn = getIteratorFn(maybeIterable); 81 | return iteratorFn && iteratorFn === maybeIterable.keys; 82 | } 83 | -------------------------------------------------------------------------------- /src/Repeat.js: -------------------------------------------------------------------------------- 1 | import { wholeSlice, resolveBegin, resolveEnd } from './TrieUtils'; 2 | import { IndexedSeq } from './Seq'; 3 | import { is } from './is'; 4 | import { Iterator, iteratorValue, iteratorDone } from './Iterator'; 5 | 6 | import deepEqual from './utils/deepEqual'; 7 | 8 | /** 9 | * Returns a lazy Seq of `value` repeated `times` times. When `times` is 10 | * undefined, returns an infinite sequence of `value`. 11 | */ 12 | export class Repeat extends IndexedSeq { 13 | constructor(value, times) { 14 | if (!(this instanceof Repeat)) { 15 | return new Repeat(value, times); 16 | } 17 | this._value = value; 18 | this.size = times === undefined ? Infinity : Math.max(0, times); 19 | if (this.size === 0) { 20 | if (EMPTY_REPEAT) { 21 | return EMPTY_REPEAT; 22 | } 23 | EMPTY_REPEAT = this; 24 | } 25 | } 26 | 27 | toString() { 28 | if (this.size === 0) { 29 | return 'Repeat []'; 30 | } 31 | return 'Repeat [ ' + this._value + ' ' + this.size + ' times ]'; 32 | } 33 | 34 | get(index, notSetValue) { 35 | return this.has(index) ? this._value : notSetValue; 36 | } 37 | 38 | includes(searchValue) { 39 | return is(this._value, searchValue); 40 | } 41 | 42 | slice(begin, end) { 43 | const size = this.size; 44 | return wholeSlice(begin, end, size) 45 | ? this 46 | : new Repeat( 47 | this._value, 48 | resolveEnd(end, size) - resolveBegin(begin, size) 49 | ); 50 | } 51 | 52 | reverse() { 53 | return this; 54 | } 55 | 56 | indexOf(searchValue) { 57 | if (is(this._value, searchValue)) { 58 | return 0; 59 | } 60 | return -1; 61 | } 62 | 63 | lastIndexOf(searchValue) { 64 | if (is(this._value, searchValue)) { 65 | return this.size; 66 | } 67 | return -1; 68 | } 69 | 70 | __iterate(fn, reverse) { 71 | const size = this.size; 72 | let i = 0; 73 | while (i !== size) { 74 | if (fn(this._value, reverse ? size - ++i : i++, this) === false) { 75 | break; 76 | } 77 | } 78 | return i; 79 | } 80 | 81 | __iterator(type, reverse) { 82 | const size = this.size; 83 | let i = 0; 84 | return new Iterator(() => 85 | i === size 86 | ? iteratorDone() 87 | : iteratorValue(type, reverse ? size - ++i : i++, this._value) 88 | ); 89 | } 90 | 91 | equals(other) { 92 | return other instanceof Repeat 93 | ? is(this._value, other._value) 94 | : deepEqual(other); 95 | } 96 | } 97 | 98 | let EMPTY_REPEAT; 99 | -------------------------------------------------------------------------------- /__tests__/hasIn.ts: -------------------------------------------------------------------------------- 1 | import { fromJS, hasIn, List, Map } from 'immutable'; 2 | 3 | describe('hasIn', () => { 4 | it('deep has', () => { 5 | const m = fromJS({ a: { b: { c: 10, d: undefined } } }); 6 | expect(m.hasIn(['a', 'b', 'c'])).toEqual(true); 7 | expect(m.hasIn(['a', 'b', 'd'])).toEqual(true); 8 | expect(m.hasIn(['a', 'b', 'z'])).toEqual(false); 9 | expect(m.hasIn(['a', 'y', 'z'])).toEqual(false); 10 | expect(hasIn(m, ['a', 'b', 'c'])).toEqual(true); 11 | expect(hasIn(m, ['a', 'b', 'z'])).toEqual(false); 12 | }); 13 | 14 | it('deep has with list as keyPath', () => { 15 | const m = fromJS({ a: { b: { c: 10 } } }); 16 | expect(m.hasIn(fromJS(['a', 'b', 'c']))).toEqual(true); 17 | expect(m.hasIn(fromJS(['a', 'b', 'z']))).toEqual(false); 18 | expect(m.hasIn(fromJS(['a', 'y', 'z']))).toEqual(false); 19 | expect(hasIn(m, fromJS(['a', 'b', 'c']))).toEqual(true); 20 | expect(hasIn(m, fromJS(['a', 'y', 'z']))).toEqual(false); 21 | }); 22 | 23 | it('deep has throws without list or array-like', () => { 24 | // @ts-expect-error 25 | expect(() => Map().hasIn(undefined)).toThrow( 26 | 'Invalid keyPath: expected Ordered Collection or Array: undefined' 27 | ); 28 | // @ts-expect-error 29 | expect(() => Map().hasIn({ a: 1, b: 2 })).toThrow( 30 | 'Invalid keyPath: expected Ordered Collection or Array: [object Object]' 31 | ); 32 | // TODO: should expect error 33 | expect(() => Map().hasIn('abc')).toThrow( 34 | 'Invalid keyPath: expected Ordered Collection or Array: abc' 35 | ); 36 | // TODO: should expect error 37 | expect(() => hasIn(Map(), 'abc')).toThrow( 38 | 'Invalid keyPath: expected Ordered Collection or Array: abc' 39 | ); 40 | }); 41 | 42 | it('deep has does not throw if non-readable path', () => { 43 | const deep = Map({ 44 | key: { regular: 'jsobj' }, 45 | list: List([Map({ num: 10 })]), 46 | }); 47 | expect(deep.hasIn(['key', 'foo', 'item'])).toBe(false); 48 | expect(deep.hasIn(['list', 0, 'num', 'badKey'])).toBe(false); 49 | expect(hasIn(deep, ['key', 'foo', 'item'])).toBe(false); 50 | expect(hasIn(deep, ['list', 0, 'num', 'badKey'])).toBe(false); 51 | }); 52 | 53 | it('deep has in plain Object and Array', () => { 54 | const m = { a: { b: { c: [10, undefined], d: undefined } } }; 55 | expect(hasIn(m, ['a', 'b', 'c', 0])).toEqual(true); 56 | expect(hasIn(m, ['a', 'b', 'c', 1])).toEqual(true); 57 | expect(hasIn(m, ['a', 'b', 'c', 2])).toEqual(false); 58 | expect(hasIn(m, ['a', 'b', 'd'])).toEqual(true); 59 | expect(hasIn(m, ['a', 'b', 'z'])).toEqual(false); 60 | expect(hasIn(m, ['a', 'b', 'z'])).toEqual(false); 61 | }); 62 | }); 63 | -------------------------------------------------------------------------------- /type-definitions/ts-tests/deepCopy.ts: -------------------------------------------------------------------------------- 1 | import { List, Map, Record, Set, Seq, DeepCopy, Collection } from 'immutable'; 2 | 3 | { 4 | // Basic types 5 | 6 | // $ExpectType { a: number; b: number; } 7 | type Test = DeepCopy<{ a: number; b: number }>; 8 | } 9 | 10 | { 11 | // Iterables 12 | 13 | // $ExpectType string[] 14 | type Test = DeepCopy; 15 | 16 | // $ExpectType number[] 17 | type Keyed = DeepCopy>; 18 | } 19 | 20 | { 21 | // Immutable first-level types 22 | 23 | // $ExpectType { [x: string]: string; } 24 | type StringKey = DeepCopy>; 25 | 26 | // should be `{ [x: string]: object; }` but there is an issue with circular references 27 | // $ExpectType { [x: string]: unknown; } 28 | type ObjectKey = DeepCopy>; 29 | 30 | // should be `{ [x: string]: object; [x: number]: object; }` but there is an issue with circular references 31 | // $ExpectType { [x: string]: unknown; [x: number]: unknown; } 32 | type MixedKey = DeepCopy>; 33 | 34 | // $ExpectType string[] 35 | type ListDeepCopy = DeepCopy>; 36 | 37 | // $ExpectType string[] 38 | type SetDeepCopy = DeepCopy>; 39 | } 40 | 41 | { 42 | // Keyed 43 | 44 | // $ExpectType { [x: string]: number; } 45 | type Keyed = DeepCopy>; 46 | 47 | // $ExpectType { [x: string]: number; [x: number]: number; } 48 | type KeyedMixed = DeepCopy>; 49 | 50 | // $ExpectType { [x: string]: number; [x: number]: number; } 51 | type KeyedSeqMixed = DeepCopy>; 52 | 53 | // $ExpectType { [x: string]: number; [x: number]: number; } 54 | type MapMixed = DeepCopy>; 55 | } 56 | 57 | { 58 | // Nested 59 | 60 | // should be `{ map: { [x: string]: string; }; list: string[]; set: string[]; }` but there is an issue with circular references 61 | // $ExpectType { map: unknown; list: unknown; set: unknown; } 62 | type NestedObject = DeepCopy<{ map: Map; list: List; set: Set; }>; 63 | 64 | // should be `{ map: { [x: string]: string; }; }`, but there is an issue with circular references 65 | // $ExpectType { map: unknown; } 66 | type NestedMap = DeepCopy>>; 67 | } 68 | 69 | { 70 | // Circular references 71 | 72 | type Article = Record<{ title: string; tag: Tag; }>; 73 | type Tag = Record<{ name: string; article: Article; }>; 74 | 75 | // should handle circular references here somehow 76 | // $ExpectType { title: string; tag: unknown; } 77 | type Circular = DeepCopy
; 78 | // ^? 79 | } 80 | -------------------------------------------------------------------------------- /website/src/ImmutableConsole.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect } from 'react'; 2 | 3 | type InstallSpace = { 4 | Immutable?: unknown; 5 | module?: unknown; 6 | exports?: unknown; 7 | }; 8 | 9 | let installingVersion: string | undefined; 10 | 11 | export function ImmutableConsole({ version }: { version: string }) { 12 | useEffect(() => { 13 | const installSpace = global as unknown as InstallSpace; 14 | if (installingVersion === version) { 15 | return; 16 | } 17 | installingVersion = version; 18 | installUMD(installSpace, getSourceURL(version)).then(Immutable => { 19 | installSpace.Immutable = Immutable; 20 | /* eslint-disable no-console */ 21 | // tslint:disable:no-console 22 | console.log( 23 | '\n' + 24 | ' ▄▟████▙▄ _ __ __ __ __ _ _ _______ ____ _ _____ \n' + 25 | ' ▟██████████▙ | | | \\ / | \\ / | | | |__ __|/\\ | _ \\| | | ___|\n' + 26 | '██████████████ | | | \\/ | \\/ | | | | | | / \\ | |_) | | | |__ \n' + 27 | '██████████████ | | | |\\ /| | |\\ /| | | | | | | / /\\ \\ | _ <| | | __| \n' + 28 | ' ▜██████████▛ | | | | \\/ | | | \\/ | | |__| | | |/ ____ \\| |_) | |___| |___ \n' + 29 | ' ▀▜████▛▀ |_| |_| |_|_| |_|\\____/ |_/_/ \\_\\____/|_____|_____|\n' + 30 | '\n' + 31 | `Version: ${version}\n` + 32 | '> console.log(Immutable);' 33 | ); 34 | console.log(Immutable); 35 | /* eslint-enable no-console */ 36 | // tslint:enable:no-console 37 | }); 38 | }, [version]); 39 | return null; 40 | } 41 | 42 | function getSourceURL(version: string) { 43 | if (version === 'latest@main') { 44 | return `https://cdn.jsdelivr.net/gh/immutable-js/immutable-js@npm/dist/immutable.js`; 45 | } 46 | const semver = version[0] === 'v' ? version.slice(1) : version; 47 | return `https://cdn.jsdelivr.net/npm/immutable@${semver}/dist/immutable.js`; 48 | } 49 | 50 | function installUMD(installSpace: InstallSpace, src: string): Promise { 51 | return new Promise(resolve => { 52 | const installedModule = (installSpace.module = { 53 | exports: (installSpace.exports = {}), 54 | }); 55 | const script = document.createElement('script'); 56 | const firstScript = document.getElementsByTagName('script')[0]; 57 | script.src = src; 58 | script.addEventListener( 59 | 'load', 60 | () => { 61 | installSpace.module = undefined; 62 | installSpace.exports = undefined; 63 | script.remove(); 64 | resolve(installedModule.exports); 65 | }, 66 | false 67 | ); 68 | firstScript?.parentNode?.insertBefore(script, firstScript); 69 | }); 70 | } 71 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "parserOptions": { 4 | "ecmaVersion": 6, 5 | "sourceType": "module" 6 | }, 7 | "extends": ["airbnb", "prettier"], 8 | "plugins": ["react", "prettier"], 9 | "rules": { 10 | "array-callback-return": "off", 11 | "block-scoped-var": "off", 12 | "class-methods-use-this": "off", 13 | "consistent-return": "off", 14 | "constructor-super": "off", 15 | "default-case": "off", 16 | "func-names": "off", 17 | "max-classes-per-file": "off", 18 | "no-bitwise": "off", 19 | "no-cond-assign": "off", 20 | "no-constant-condition": "off", 21 | "no-continue": "off", 22 | "no-else-return": "error", 23 | "no-empty": "error", 24 | "no-lonely-if": "error", 25 | "no-multi-assign": "off", 26 | "no-nested-ternary": "off", 27 | "no-param-reassign": "off", 28 | "no-plusplus": "off", 29 | "no-prototype-builtins": "off", 30 | "no-restricted-syntax": "off", 31 | "no-return-assign": "off", 32 | "no-self-compare": "off", 33 | "no-sequences": "off", 34 | "no-shadow": "off", 35 | "no-this-before-super": "off", 36 | "no-underscore-dangle": "off", 37 | "no-unused-expressions": "off", 38 | "no-unused-vars": "error", 39 | "no-use-before-define": "off", 40 | "no-useless-concat": "error", 41 | "no-var": "error", 42 | "object-shorthand": "off", 43 | "one-var": "error", 44 | "operator-assignment": "error", 45 | "prefer-destructuring": "off", 46 | "prefer-rest-params": "off", 47 | "prefer-spread": "off", 48 | "prefer-template": "off", 49 | "spaced-comment": "off", 50 | "vars-on-top": "off", 51 | "react/destructuring-assignment": "off", 52 | "react/jsx-boolean-value": "off", 53 | "react/jsx-curly-brace-presence": "off", 54 | "react/jsx-filename-extension": "off", 55 | "react/no-array-index-key": "off", 56 | "react/no-danger": "off", 57 | "react/no-multi-comp": "off", 58 | "react/prefer-es6-class": "off", 59 | "react/prefer-stateless-function": "off", 60 | "react/prop-types": "off", 61 | "react/self-closing-comp": "error", 62 | "react/sort-comp": "off", 63 | "react/jsx-props-no-spreading": "off", 64 | "jsx-a11y/no-static-element-interactions": "off", 65 | "import/extensions": [ 66 | "error", 67 | "always", 68 | { "js": "never", "ts": "never", "tsx": "never" } 69 | ], 70 | "import/newline-after-import": "error", 71 | "import/no-cycle": "off", 72 | "import/no-extraneous-dependencies": "off", 73 | "import/no-mutable-exports": "error", 74 | "import/no-unresolved": "error", 75 | "import/no-useless-path-segments": "off", 76 | "import/prefer-default-export": "off", 77 | "prettier/prettier": "error" 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /__tests__/count.ts: -------------------------------------------------------------------------------- 1 | import { Range, Seq } from 'immutable'; 2 | 3 | describe('count', () => { 4 | it('counts sequences with known lengths', () => { 5 | expect(Seq([1, 2, 3, 4, 5]).size).toBe(5); 6 | expect(Seq([1, 2, 3, 4, 5]).count()).toBe(5); 7 | }); 8 | 9 | it('counts sequences with unknown lengths, resulting in a cached size', () => { 10 | const seq = Seq([1, 2, 3, 4, 5, 6]).filter(x => x % 2 === 0); 11 | expect(seq.size).toBe(undefined); 12 | expect(seq.count()).toBe(3); 13 | expect(seq.size).toBe(3); 14 | }); 15 | 16 | it('counts sequences with a specific predicate', () => { 17 | const seq = Seq([1, 2, 3, 4, 5, 6]); 18 | expect(seq.size).toBe(6); 19 | expect(seq.count(x => x > 3)).toBe(3); 20 | }); 21 | 22 | describe('countBy', () => { 23 | it('counts by keyed sequence', () => { 24 | const grouped = Seq({ a: 1, b: 2, c: 3, d: 4 }).countBy(x => x % 2); 25 | expect(grouped.toJS()).toEqual({ 1: 2, 0: 2 }); 26 | expect(grouped.get(1)).toEqual(2); 27 | }); 28 | 29 | it('counts by indexed sequence', () => { 30 | expect( 31 | Seq([1, 2, 3, 4, 5, 6]) 32 | .countBy(x => x % 2) 33 | .toJS() 34 | ).toEqual({ 1: 3, 0: 3 }); 35 | }); 36 | 37 | it('counts by specific keys', () => { 38 | expect( 39 | Seq([1, 2, 3, 4, 5, 6]) 40 | .countBy(x => (x % 2 ? 'odd' : 'even')) 41 | .toJS() 42 | ).toEqual({ odd: 3, even: 3 }); 43 | }); 44 | }); 45 | 46 | describe('isEmpty', () => { 47 | it('is O(1) on sequences with known lengths', () => { 48 | expect(Seq([1, 2, 3, 4, 5]).size).toBe(5); 49 | expect(Seq([1, 2, 3, 4, 5]).isEmpty()).toBe(false); 50 | expect(Seq().size).toBe(0); 51 | expect(Seq().isEmpty()).toBe(true); 52 | }); 53 | 54 | it('lazily evaluates Seq with unknown length', () => { 55 | let seq = Seq([1, 2, 3, 4, 5, 6]).filter(x => x % 2 === 0); 56 | expect(seq.size).toBe(undefined); 57 | expect(seq.isEmpty()).toBe(false); 58 | expect(seq.size).toBe(undefined); 59 | 60 | seq = Seq([1, 2, 3, 4, 5, 6]).filter(x => x > 10); 61 | expect(seq.size).toBe(undefined); 62 | expect(seq.isEmpty()).toBe(true); 63 | expect(seq.size).toBe(undefined); 64 | }); 65 | 66 | it('with infinitely long sequences of known length', () => { 67 | const seq = Range(); 68 | expect(seq.size).toBe(Infinity); 69 | expect(seq.isEmpty()).toBe(false); 70 | }); 71 | 72 | it('with infinitely long sequences of unknown length', () => { 73 | const seq = Range().filter(x => x % 2 === 0); 74 | expect(seq.size).toBe(undefined); 75 | expect(seq.isEmpty()).toBe(false); 76 | expect(seq.size).toBe(undefined); 77 | }); 78 | }); 79 | }); 80 | -------------------------------------------------------------------------------- /perf/List.js: -------------------------------------------------------------------------------- 1 | describe('List', function () { 2 | describe('builds from array', function () { 3 | var array2 = []; 4 | for (var ii = 0; ii < 2; ii++) { 5 | array2[ii] = ii; 6 | } 7 | 8 | it('of 2', function () { 9 | Immutable.List(array2); 10 | }); 11 | 12 | var array8 = []; 13 | for (var ii = 0; ii < 8; ii++) { 14 | array8[ii] = ii; 15 | } 16 | 17 | it('of 8', function () { 18 | Immutable.List(array8); 19 | }); 20 | 21 | var array32 = []; 22 | for (var ii = 0; ii < 32; ii++) { 23 | array32[ii] = ii; 24 | } 25 | 26 | it('of 32', function () { 27 | Immutable.List(array32); 28 | }); 29 | 30 | var array1024 = []; 31 | for (var ii = 0; ii < 1024; ii++) { 32 | array1024[ii] = ii; 33 | } 34 | 35 | it('of 1024', function () { 36 | Immutable.List(array1024); 37 | }); 38 | }); 39 | 40 | describe('pushes into', function () { 41 | it('2 times', function () { 42 | var list = Immutable.List(); 43 | for (var ii = 0; ii < 2; ii++) { 44 | list = list.push(ii); 45 | } 46 | }); 47 | 48 | it('8 times', function () { 49 | var list = Immutable.List(); 50 | for (var ii = 0; ii < 8; ii++) { 51 | list = list.push(ii); 52 | } 53 | }); 54 | 55 | it('32 times', function () { 56 | var list = Immutable.List(); 57 | for (var ii = 0; ii < 32; ii++) { 58 | list = list.push(ii); 59 | } 60 | }); 61 | 62 | it('1024 times', function () { 63 | var list = Immutable.List(); 64 | for (var ii = 0; ii < 1024; ii++) { 65 | list = list.push(ii); 66 | } 67 | }); 68 | }); 69 | 70 | describe('pushes into transient', function () { 71 | it('2 times', function () { 72 | var list = Immutable.List().asMutable(); 73 | for (var ii = 0; ii < 2; ii++) { 74 | list = list.push(ii); 75 | } 76 | list = list.asImmutable(); 77 | }); 78 | 79 | it('8 times', function () { 80 | var list = Immutable.List().asMutable(); 81 | for (var ii = 0; ii < 8; ii++) { 82 | list = list.push(ii); 83 | } 84 | list = list.asImmutable(); 85 | }); 86 | 87 | it('32 times', function () { 88 | var list = Immutable.List().asMutable(); 89 | for (var ii = 0; ii < 32; ii++) { 90 | list = list.push(ii); 91 | } 92 | list = list.asImmutable(); 93 | }); 94 | 95 | it('1024 times', function () { 96 | var list = Immutable.List().asMutable(); 97 | for (var ii = 0; ii < 1024; ii++) { 98 | list = list.push(ii); 99 | } 100 | list = list.asImmutable(); 101 | }); 102 | }); 103 | }); 104 | -------------------------------------------------------------------------------- /__tests__/MultiRequire.js: -------------------------------------------------------------------------------- 1 | const Immutable1 = require('../src/Immutable'); 2 | 3 | jest.resetModules(); 4 | 5 | const Immutable2 = require('../src/Immutable'); 6 | 7 | describe('MultiRequire', () => { 8 | it.skip('might require two different instances of Immutable', () => { 9 | expect(Immutable1).not.toBe(Immutable2); 10 | expect(Immutable1.Map({ a: 1 }).toJS()).toEqual({ a: 1 }); 11 | expect(Immutable2.Map({ a: 1 }).toJS()).toEqual({ a: 1 }); 12 | }); 13 | 14 | it('detects sequences', () => { 15 | const x = Immutable1.Map({ a: 1 }); 16 | const y = Immutable2.Map({ a: 1 }); 17 | expect(Immutable1.isCollection(y)).toBe(true); 18 | expect(Immutable2.isCollection(x)).toBe(true); 19 | }); 20 | 21 | it('detects records', () => { 22 | const R1 = Immutable1.Record({ a: 1 }); 23 | const R2 = Immutable2.Record({ a: 1 }); 24 | expect(Immutable1.Record.isRecord(R2())).toBe(true); 25 | expect(Immutable2.Record.isRecord(R1())).toBe(true); 26 | }); 27 | 28 | it('converts to JS when inter-nested', () => { 29 | const deep = Immutable1.Map({ 30 | a: 1, 31 | b: 2, 32 | c: Immutable2.Map({ 33 | x: 3, 34 | y: 4, 35 | z: Immutable1.Map(), 36 | }), 37 | }); 38 | 39 | expect(deep.toJS()).toEqual({ 40 | a: 1, 41 | b: 2, 42 | c: { 43 | x: 3, 44 | y: 4, 45 | z: {}, 46 | }, 47 | }); 48 | }); 49 | 50 | it('compares for equality', () => { 51 | const x = Immutable1.Map({ a: 1 }); 52 | const y = Immutable2.Map({ a: 1 }); 53 | expect(Immutable1.is(x, y)).toBe(true); 54 | expect(Immutable2.is(x, y)).toBe(true); 55 | }); 56 | 57 | it('flattens nested values', () => { 58 | const nested = Immutable1.List( 59 | Immutable2.List(Immutable1.List(Immutable2.List.of(1, 2))) 60 | ); 61 | 62 | expect(nested.flatten().toJS()).toEqual([1, 2]); 63 | }); 64 | 65 | it('detects types', () => { 66 | let c1 = Immutable1.Map(); 67 | let c2 = Immutable2.Map(); 68 | expect(Immutable1.Map.isMap(c2)).toBe(true); 69 | expect(Immutable2.Map.isMap(c1)).toBe(true); 70 | 71 | c1 = Immutable1.OrderedMap(); 72 | c2 = Immutable2.OrderedMap(); 73 | expect(Immutable1.OrderedMap.isOrderedMap(c2)).toBe(true); 74 | expect(Immutable2.OrderedMap.isOrderedMap(c1)).toBe(true); 75 | 76 | c1 = Immutable1.List(); 77 | c2 = Immutable2.List(); 78 | expect(Immutable1.List.isList(c2)).toBe(true); 79 | expect(Immutable2.List.isList(c1)).toBe(true); 80 | 81 | c1 = Immutable1.Stack(); 82 | c2 = Immutable2.Stack(); 83 | expect(Immutable1.Stack.isStack(c2)).toBe(true); 84 | expect(Immutable2.Stack.isStack(c1)).toBe(true); 85 | 86 | c1 = Immutable1.Set(); 87 | c2 = Immutable2.Set(); 88 | expect(Immutable1.Set.isSet(c2)).toBe(true); 89 | expect(Immutable2.Set.isSet(c1)).toBe(true); 90 | }); 91 | }); 92 | -------------------------------------------------------------------------------- /src/TrieUtils.js: -------------------------------------------------------------------------------- 1 | // Used for setting prototype methods that IE8 chokes on. 2 | export const DELETE = 'delete'; 3 | 4 | // Constants describing the size of trie nodes. 5 | export const SHIFT = 5; // Resulted in best performance after ______? 6 | export const SIZE = 1 << SHIFT; 7 | export const MASK = SIZE - 1; 8 | 9 | // A consistent shared value representing "not set" which equals nothing other 10 | // than itself, and nothing that could be provided externally. 11 | export const NOT_SET = {}; 12 | 13 | // Boolean references, Rough equivalent of `bool &`. 14 | export function MakeRef() { 15 | return { value: false }; 16 | } 17 | 18 | export function SetRef(ref) { 19 | if (ref) { 20 | ref.value = true; 21 | } 22 | } 23 | 24 | // A function which returns a value representing an "owner" for transient writes 25 | // to tries. The return value will only ever equal itself, and will not equal 26 | // the return of any subsequent call of this function. 27 | export function OwnerID() {} 28 | 29 | export function ensureSize(iter) { 30 | if (iter.size === undefined) { 31 | iter.size = iter.__iterate(returnTrue); 32 | } 33 | return iter.size; 34 | } 35 | 36 | export function wrapIndex(iter, index) { 37 | // This implements "is array index" which the ECMAString spec defines as: 38 | // 39 | // A String property name P is an array index if and only if 40 | // ToString(ToUint32(P)) is equal to P and ToUint32(P) is not equal 41 | // to 2^32−1. 42 | // 43 | // http://www.ecma-international.org/ecma-262/6.0/#sec-array-exotic-objects 44 | if (typeof index !== 'number') { 45 | const uint32Index = index >>> 0; // N >>> 0 is shorthand for ToUint32 46 | if ('' + uint32Index !== index || uint32Index === 4294967295) { 47 | return NaN; 48 | } 49 | index = uint32Index; 50 | } 51 | return index < 0 ? ensureSize(iter) + index : index; 52 | } 53 | 54 | export function returnTrue() { 55 | return true; 56 | } 57 | 58 | export function wholeSlice(begin, end, size) { 59 | return ( 60 | ((begin === 0 && !isNeg(begin)) || 61 | (size !== undefined && begin <= -size)) && 62 | (end === undefined || (size !== undefined && end >= size)) 63 | ); 64 | } 65 | 66 | export function resolveBegin(begin, size) { 67 | return resolveIndex(begin, size, 0); 68 | } 69 | 70 | export function resolveEnd(end, size) { 71 | return resolveIndex(end, size, size); 72 | } 73 | 74 | function resolveIndex(index, size, defaultIndex) { 75 | // Sanitize indices using this shorthand for ToInt32(argument) 76 | // http://www.ecma-international.org/ecma-262/6.0/#sec-toint32 77 | return index === undefined 78 | ? defaultIndex 79 | : isNeg(index) 80 | ? size === Infinity 81 | ? size 82 | : Math.max(0, size + index) | 0 83 | : size === undefined || size === index 84 | ? index 85 | : Math.min(size, index) | 0; 86 | } 87 | 88 | function isNeg(value) { 89 | // Account for -0 which is negative, but not less than 0. 90 | return value < 0 || (value === 0 && 1 / value === -Infinity); 91 | } 92 | -------------------------------------------------------------------------------- /__tests__/partition.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Collection, 3 | isAssociative, 4 | isIndexed, 5 | isKeyed, 6 | isList, 7 | isMap, 8 | isSeq, 9 | isSet, 10 | List, 11 | Map as IMap, 12 | Seq, 13 | Set as ISet, 14 | } from 'immutable'; 15 | 16 | describe('partition', () => { 17 | let isOdd: jest.Mock; 18 | 19 | beforeEach(() => { 20 | isOdd = jest.fn(x => x % 2); 21 | }); 22 | 23 | it('partitions keyed sequence', () => { 24 | const parts = Seq({ a: 1, b: 2, c: 3, d: 4 }).partition(isOdd); 25 | expect(isKeyed(parts[0])).toBe(true); 26 | expect(isSeq(parts[0])).toBe(true); 27 | expect(parts.map(part => part.toJS())).toEqual([ 28 | { b: 2, d: 4 }, 29 | { a: 1, c: 3 }, 30 | ]); 31 | expect(isOdd.mock.calls.length).toBe(4); 32 | 33 | // Each group should be a keyed sequence, not an indexed sequence 34 | const trueGroup = parts[1]; 35 | expect(trueGroup && trueGroup.toArray()).toEqual([ 36 | ['a', 1], 37 | ['c', 3], 38 | ]); 39 | }); 40 | 41 | it('partitions indexed sequence', () => { 42 | const parts = Seq([1, 2, 3, 4, 5, 6]).partition(isOdd); 43 | expect(isIndexed(parts[0])).toBe(true); 44 | expect(isSeq(parts[0])).toBe(true); 45 | expect(parts.map(part => part.toJS())).toEqual([ 46 | [2, 4, 6], 47 | [1, 3, 5], 48 | ]); 49 | expect(isOdd.mock.calls.length).toBe(6); 50 | }); 51 | 52 | it('partitions set sequence', () => { 53 | const parts = Seq.Set([1, 2, 3, 4, 5, 6]).partition(isOdd); 54 | expect(isAssociative(parts[0])).toBe(false); 55 | expect(isSeq(parts[0])).toBe(true); 56 | expect(parts.map(part => part.toJS())).toEqual([ 57 | [2, 4, 6], 58 | [1, 3, 5], 59 | ]); 60 | expect(isOdd.mock.calls.length).toBe(6); 61 | }); 62 | 63 | it('partitions keyed collection', () => { 64 | const parts = IMap({ a: 1, b: 2, c: 3, d: 4 }).partition(isOdd); 65 | expect(isMap(parts[0])).toBe(true); 66 | expect(isSeq(parts[0])).toBe(false); 67 | expect(parts.map(part => part.toJS())).toEqual([ 68 | { b: 2, d: 4 }, 69 | { a: 1, c: 3 }, 70 | ]); 71 | expect(isOdd.mock.calls.length).toBe(4); 72 | 73 | // Each group should be a keyed collection, not an indexed collection 74 | const trueGroup = parts[1]; 75 | expect(trueGroup && trueGroup.toArray()).toEqual([ 76 | ['a', 1], 77 | ['c', 3], 78 | ]); 79 | }); 80 | 81 | it('partitions indexed collection', () => { 82 | const parts = List([1, 2, 3, 4, 5, 6]).partition(isOdd); 83 | expect(isList(parts[0])).toBe(true); 84 | expect(isSeq(parts[0])).toBe(false); 85 | expect(parts.map(part => part.toJS())).toEqual([ 86 | [2, 4, 6], 87 | [1, 3, 5], 88 | ]); 89 | expect(isOdd.mock.calls.length).toBe(6); 90 | }); 91 | 92 | it('partitions set collection', () => { 93 | const parts = ISet([1, 2, 3, 4, 5, 6]).partition(isOdd); 94 | expect(isSet(parts[0])).toBe(true); 95 | expect(isSeq(parts[0])).toBe(false); 96 | expect(parts.map(part => part.toJS().sort())).toEqual([ 97 | [2, 4, 6], 98 | [1, 3, 5], 99 | ]); 100 | expect(isOdd.mock.calls.length).toBe(6); 101 | }); 102 | }); 103 | -------------------------------------------------------------------------------- /__tests__/ArraySeq.ts: -------------------------------------------------------------------------------- 1 | import { Seq } from 'immutable'; 2 | 3 | describe('ArraySequence', () => { 4 | it('every is true when predicate is true for all entries', () => { 5 | expect(Seq([]).every(() => false)).toBe(true); 6 | expect(Seq([1, 2, 3]).every(v => v > 0)).toBe(true); 7 | expect(Seq([1, 2, 3]).every(v => v < 3)).toBe(false); 8 | }); 9 | 10 | it('some is true when predicate is true for any entry', () => { 11 | expect(Seq([]).some(() => true)).toBe(false); 12 | expect(Seq([1, 2, 3]).some(v => v > 0)).toBe(true); 13 | expect(Seq([1, 2, 3]).some(v => v < 3)).toBe(true); 14 | expect(Seq([1, 2, 3]).some(v => v > 1)).toBe(true); 15 | expect(Seq([1, 2, 3]).some(v => v < 0)).toBe(false); 16 | }); 17 | 18 | it('maps', () => { 19 | const i = Seq([1, 2, 3]); 20 | const m = i.map(x => x + x).toArray(); 21 | expect(m).toEqual([2, 4, 6]); 22 | }); 23 | 24 | it('reduces', () => { 25 | const i = Seq([1, 2, 3]); 26 | const r = i.reduce((acc, x) => acc + x); 27 | expect(r).toEqual(6); 28 | }); 29 | 30 | it('efficiently chains iteration methods', () => { 31 | const i = Seq('abcdefghijklmnopqrstuvwxyz'.split('')); 32 | function studly(letter, index) { 33 | return index % 2 === 0 ? letter : letter.toUpperCase(); 34 | } 35 | const result = i 36 | .reverse() 37 | .take(10) 38 | .reverse() 39 | .take(5) 40 | .map(studly) 41 | .toArray() 42 | .join(''); 43 | expect(result).toBe('qRsTu'); 44 | }); 45 | 46 | it('counts from the end of the sequence on negative index', () => { 47 | const i = Seq([1, 2, 3, 4, 5, 6, 7]); 48 | expect(i.get(-1)).toBe(7); 49 | expect(i.get(-5)).toBe(3); 50 | expect(i.get(-9)).toBe(undefined); 51 | expect(i.get(-999, 1000)).toBe(1000); 52 | }); 53 | 54 | it('handles trailing holes', () => { 55 | const a = [1, 2, 3]; 56 | a.length = 10; 57 | const seq = Seq(a); 58 | expect(seq.size).toBe(10); 59 | expect(seq.toArray().length).toBe(10); 60 | expect(seq.map(x => x * x).size).toBe(10); 61 | expect(seq.map(x => x * x).toArray().length).toBe(10); 62 | expect(seq.skip(2).toArray().length).toBe(8); 63 | expect(seq.take(2).toArray().length).toBe(2); 64 | expect(seq.take(5).toArray().length).toBe(5); 65 | expect(seq.filter(x => x % 2 === 1).toArray().length).toBe(2); 66 | expect(seq.toKeyedSeq().flip().size).toBe(10); 67 | expect(seq.toKeyedSeq().flip().flip().size).toBe(10); 68 | expect(seq.toKeyedSeq().flip().flip().toArray().length).toBe(10); 69 | }); 70 | 71 | it('can be iterated', () => { 72 | const a = [1, 2, 3]; 73 | const seq = Seq(a); 74 | const entries = seq.entries(); 75 | expect(entries.next()).toEqual({ value: [0, 1], done: false }); 76 | expect(entries.next()).toEqual({ value: [1, 2], done: false }); 77 | expect(entries.next()).toEqual({ value: [2, 3], done: false }); 78 | expect(entries.next()).toEqual({ value: undefined, done: true }); 79 | }); 80 | 81 | it('cannot be mutated after calling toArray', () => { 82 | const seq = Seq(['A', 'B', 'C']); 83 | 84 | const firstReverse = Seq(seq.toArray().reverse()); 85 | const secondReverse = Seq(seq.toArray().reverse()); 86 | 87 | expect(firstReverse.get(0)).toEqual('C'); 88 | expect(secondReverse.get(0)).toEqual('C'); 89 | }); 90 | }); 91 | -------------------------------------------------------------------------------- /__tests__/groupBy.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Collection, 3 | Map, 4 | Seq, 5 | isOrdered, 6 | OrderedMap, 7 | List, 8 | OrderedSet, 9 | Set, 10 | Stack, 11 | Record, 12 | } from 'immutable'; 13 | 14 | describe('groupBy', () => { 15 | it.each` 16 | constructor | constructorIsOrdered | isObject 17 | ${Collection} | ${true} | ${false} 18 | ${List} | ${true} | ${false} 19 | ${Seq} | ${true} | ${false} 20 | ${Set} | ${false} | ${false} 21 | ${Stack} | ${true} | ${false} 22 | ${OrderedSet} | ${true} | ${false} 23 | ${Map} | ${false} | ${true} 24 | ${OrderedMap} | ${true} | ${true} 25 | `( 26 | 'groupBy returns ordered or unordered of the base type is ordered or not: $constructor.name', 27 | ({ constructor, constructorIsOrdered, isObject }) => { 28 | const iterableConstructor = ['a', 'b', 'a', 'c']; 29 | const objectConstructor = { a: 1, b: 2, c: 3, d: 1 }; 30 | 31 | const col = constructor( 32 | isObject ? objectConstructor : iterableConstructor 33 | ); 34 | 35 | const grouped = col.groupBy(v => v); 36 | 37 | // all groupBy should be instance of Map 38 | expect(grouped).toBeInstanceOf(Map); 39 | 40 | // ordered objects should be instance of OrderedMap 41 | expect(isOrdered(col)).toBe(constructorIsOrdered); 42 | expect(isOrdered(grouped)).toBe(constructorIsOrdered); 43 | if (constructorIsOrdered) { 44 | expect(grouped).toBeInstanceOf(OrderedMap); 45 | } else { 46 | expect(grouped).not.toBeInstanceOf(OrderedMap); 47 | } 48 | } 49 | ); 50 | 51 | it('groups keyed sequence', () => { 52 | const grouped = Seq({ a: 1, b: 2, c: 3, d: 4 }).groupBy(x => x % 2); 53 | expect(grouped.toJS()).toEqual({ 1: { a: 1, c: 3 }, 0: { b: 2, d: 4 } }); 54 | 55 | // Each group should be a keyed sequence, not an indexed sequence 56 | const firstGroup = grouped.get(1); 57 | expect(firstGroup && firstGroup.toArray()).toEqual([ 58 | ['a', 1], 59 | ['c', 3], 60 | ]); 61 | }); 62 | 63 | it('groups indexed sequence', () => { 64 | const group = Seq([1, 2, 3, 4, 5, 6]).groupBy(x => x % 2); 65 | 66 | expect(group.toJS()).toEqual({ 1: [1, 3, 5], 0: [2, 4, 6] }); 67 | }); 68 | 69 | it('groups to keys', () => { 70 | const group = Seq([1, 2, 3, 4, 5, 6]).groupBy(x => 71 | x % 2 ? 'odd' : 'even' 72 | ); 73 | expect(group.toJS()).toEqual({ odd: [1, 3, 5], even: [2, 4, 6] }); 74 | }); 75 | 76 | it('groups indexed sequences, maintaining indicies when keyed sequences', () => { 77 | const group = Seq([1, 2, 3, 4, 5, 6]).groupBy(x => x % 2); 78 | 79 | expect(group.toJS()).toEqual({ 1: [1, 3, 5], 0: [2, 4, 6] }); 80 | 81 | const keyedGroup = Seq([1, 2, 3, 4, 5, 6]) 82 | .toKeyedSeq() 83 | .groupBy(x => x % 2); 84 | 85 | expect(keyedGroup.toJS()).toEqual({ 86 | 1: { 0: 1, 2: 3, 4: 5 }, 87 | 0: { 1: 2, 3: 4, 5: 6 }, 88 | }); 89 | }); 90 | 91 | it('has groups that can be mapped', () => { 92 | const mappedGroup = Seq([1, 2, 3, 4, 5, 6]) 93 | .groupBy(x => x % 2) 94 | .map(group => group.map(value => value * 10)); 95 | 96 | expect(mappedGroup.toJS()).toEqual({ 1: [10, 30, 50], 0: [20, 40, 60] }); 97 | }); 98 | }); 99 | -------------------------------------------------------------------------------- /src/is.js: -------------------------------------------------------------------------------- 1 | import { isValueObject } from './predicates/isValueObject'; 2 | 3 | /** 4 | * An extension of the "same-value" algorithm as [described for use by ES6 Map 5 | * and Set](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map#Key_equality) 6 | * 7 | * NaN is considered the same as NaN, however -0 and 0 are considered the same 8 | * value, which is different from the algorithm described by 9 | * [`Object.is`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is). 10 | * 11 | * This is extended further to allow Objects to describe the values they 12 | * represent, by way of `valueOf` or `equals` (and `hashCode`). 13 | * 14 | * Note: because of this extension, the key equality of Immutable.Map and the 15 | * value equality of Immutable.Set will differ from ES6 Map and Set. 16 | * 17 | * ### Defining custom values 18 | * 19 | * The easiest way to describe the value an object represents is by implementing 20 | * `valueOf`. For example, `Date` represents a value by returning a unix 21 | * timestamp for `valueOf`: 22 | * 23 | * var date1 = new Date(1234567890000); // Fri Feb 13 2009 ... 24 | * var date2 = new Date(1234567890000); 25 | * date1.valueOf(); // 1234567890000 26 | * assert( date1 !== date2 ); 27 | * assert( Immutable.is( date1, date2 ) ); 28 | * 29 | * Note: overriding `valueOf` may have other implications if you use this object 30 | * where JavaScript expects a primitive, such as implicit string coercion. 31 | * 32 | * For more complex types, especially collections, implementing `valueOf` may 33 | * not be performant. An alternative is to implement `equals` and `hashCode`. 34 | * 35 | * `equals` takes another object, presumably of similar type, and returns true 36 | * if it is equal. Equality is symmetrical, so the same result should be 37 | * returned if this and the argument are flipped. 38 | * 39 | * assert( a.equals(b) === b.equals(a) ); 40 | * 41 | * `hashCode` returns a 32bit integer number representing the object which will 42 | * be used to determine how to store the value object in a Map or Set. You must 43 | * provide both or neither methods, one must not exist without the other. 44 | * 45 | * Also, an important relationship between these methods must be upheld: if two 46 | * values are equal, they *must* return the same hashCode. If the values are not 47 | * equal, they might have the same hashCode; this is called a hash collision, 48 | * and while undesirable for performance reasons, it is acceptable. 49 | * 50 | * if (a.equals(b)) { 51 | * assert( a.hashCode() === b.hashCode() ); 52 | * } 53 | * 54 | * All Immutable collections are Value Objects: they implement `equals()` 55 | * and `hashCode()`. 56 | */ 57 | export function is(valueA, valueB) { 58 | if (valueA === valueB || (valueA !== valueA && valueB !== valueB)) { 59 | return true; 60 | } 61 | if (!valueA || !valueB) { 62 | return false; 63 | } 64 | if ( 65 | typeof valueA.valueOf === 'function' && 66 | typeof valueB.valueOf === 'function' 67 | ) { 68 | valueA = valueA.valueOf(); 69 | valueB = valueB.valueOf(); 70 | if (valueA === valueB || (valueA !== valueA && valueB !== valueB)) { 71 | return true; 72 | } 73 | if (!valueA || !valueB) { 74 | return false; 75 | } 76 | } 77 | return !!( 78 | isValueObject(valueA) && 79 | isValueObject(valueB) && 80 | valueA.equals(valueB) 81 | ); 82 | } 83 | -------------------------------------------------------------------------------- /.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Have a question? 2 | 3 | Please ask questions on [Stack Overflow](https://stackoverflow.com/questions/tagged/immutable.js) instead of opening a Github Issue. There are more people on Stack Overflow who 4 | can answer questions, and good answers can be searchable and canonical. 5 | 6 | # Issues 7 | 8 | We use GitHub issues to track bugs. Please ensure your bug description is clear 9 | and has sufficient instructions to be able to reproduce the issue. 10 | 11 | The absolute best way to report a bug is to submit a pull request including a 12 | new failing test which describes the bug. When the bug is fixed, your pull 13 | request can then be merged! 14 | 15 | The next best way to report a bug is to provide a reduced test case on jsFiddle 16 | or jsBin or produce exact code inline in the issue which will reproduce the bug. 17 | 18 | # Code of Conduct 19 | 20 | Immutable.js is maintained within the [Contributor Covenant's Code of Conduct](https://www.contributor-covenant.org/version/2/0/code_of_conduct/). 21 | 22 | # Pull Requests 23 | 24 | All active development of Immutable JS happens on GitHub. We actively welcome 25 | your [pull requests](https://help.github.com/articles/creating-a-pull-request). 26 | 27 | 1. Fork the repo and create your branch from `master`. 28 | 2. Install all dependencies. (`npm install`) 29 | 3. If you've added code, add tests. 30 | 4. If you've changed APIs, update the documentation. 31 | 5. Build generated JS, run tests and ensure your code passes lint. (`npm run test`) 32 | 6. If you haven't already, complete the Contributor License Agreement ("CLA"). 33 | 34 | ## Documentation 35 | 36 | Documentation for Immutable.js (hosted at http://immutable-js.github.io/immutable-js) 37 | is developed in `pages/`. Run `npm start` to get a local copy in your browser 38 | while making edits. 39 | 40 | ## Coding Style 41 | 42 | - 2 spaces for indentation (no tabs) 43 | - 80 character line length strongly preferred. 44 | - Prefer `'` over `"` 45 | - ES6 Harmony when possible. 46 | - Use semicolons; 47 | - Trailing commas, 48 | - Avd abbr wrds. 49 | 50 | # Functionality Testing 51 | 52 | Run the following command to build the library and test functionality: 53 | 54 | ```bash 55 | npm run test 56 | ``` 57 | 58 | ## Performance Regression Testing 59 | 60 | Performance tests run against master and your feature branch. 61 | Make sure to commit your changes in your local feature branch before proceeding. 62 | 63 | These commands assume you have a remote named `upstream` amd that you do not already have a local `master` branch: 64 | 65 | ```bash 66 | git fetch upstream 67 | git checkout -b master upstream/master 68 | ``` 69 | 70 | These commands build `dist` and commit `dist/immutable.js` to `master` so that the regression tests can run. 71 | ```bash 72 | npm run test 73 | git add dist/immutable.js -f 74 | git commit -m 'perf test prerequisite.' 75 | ``` 76 | 77 | Switch back to your feature branch, and run the following command to run regression tests: 78 | 79 | ```bash 80 | npm run test 81 | npm run perf 82 | ``` 83 | 84 | Sample output: 85 | 86 | ```bash 87 | > immutable@4.0.0-rc.9 perf ~/github.com/immutable-js/immutable-js 88 | > node ./resources/bench.js 89 | 90 | List > builds from array of 2 91 | Old: 678,974 683,071 687,218 ops/sec 92 | New: 669,012 673,553 678,157 ops/sec 93 | compare: 1 -1 94 | diff: -1.4% 95 | rme: 0.64% 96 | ``` 97 | 98 | ## License 99 | 100 | By contributing to Immutable.js, you agree that your contributions will be 101 | licensed under its MIT license. 102 | -------------------------------------------------------------------------------- /src/functional/merge.js: -------------------------------------------------------------------------------- 1 | import { isImmutable } from '../predicates/isImmutable'; 2 | import { isIndexed } from '../predicates/isIndexed'; 3 | import { isKeyed } from '../predicates/isKeyed'; 4 | import { IndexedCollection, KeyedCollection, Seq } from '../Seq'; 5 | import hasOwnProperty from '../utils/hasOwnProperty'; 6 | import isDataStructure from '../utils/isDataStructure'; 7 | import shallowCopy from '../utils/shallowCopy'; 8 | 9 | export function merge(collection, ...sources) { 10 | return mergeWithSources(collection, sources); 11 | } 12 | 13 | export function mergeWith(merger, collection, ...sources) { 14 | return mergeWithSources(collection, sources, merger); 15 | } 16 | 17 | export function mergeDeep(collection, ...sources) { 18 | return mergeDeepWithSources(collection, sources); 19 | } 20 | 21 | export function mergeDeepWith(merger, collection, ...sources) { 22 | return mergeDeepWithSources(collection, sources, merger); 23 | } 24 | 25 | export function mergeDeepWithSources(collection, sources, merger) { 26 | return mergeWithSources(collection, sources, deepMergerWith(merger)); 27 | } 28 | 29 | export function mergeWithSources(collection, sources, merger) { 30 | if (!isDataStructure(collection)) { 31 | throw new TypeError( 32 | 'Cannot merge into non-data-structure value: ' + collection 33 | ); 34 | } 35 | if (isImmutable(collection)) { 36 | return typeof merger === 'function' && collection.mergeWith 37 | ? collection.mergeWith(merger, ...sources) 38 | : collection.merge 39 | ? collection.merge(...sources) 40 | : collection.concat(...sources); 41 | } 42 | const isArray = Array.isArray(collection); 43 | let merged = collection; 44 | const Collection = isArray ? IndexedCollection : KeyedCollection; 45 | const mergeItem = isArray 46 | ? value => { 47 | // Copy on write 48 | if (merged === collection) { 49 | merged = shallowCopy(merged); 50 | } 51 | merged.push(value); 52 | } 53 | : (value, key) => { 54 | const hasVal = hasOwnProperty.call(merged, key); 55 | const nextVal = 56 | hasVal && merger ? merger(merged[key], value, key) : value; 57 | if (!hasVal || nextVal !== merged[key]) { 58 | // Copy on write 59 | if (merged === collection) { 60 | merged = shallowCopy(merged); 61 | } 62 | merged[key] = nextVal; 63 | } 64 | }; 65 | for (let i = 0; i < sources.length; i++) { 66 | Collection(sources[i]).forEach(mergeItem); 67 | } 68 | return merged; 69 | } 70 | 71 | function deepMergerWith(merger) { 72 | function deepMerger(oldValue, newValue, key) { 73 | return isDataStructure(oldValue) && 74 | isDataStructure(newValue) && 75 | areMergeable(oldValue, newValue) 76 | ? mergeWithSources(oldValue, [newValue], deepMerger) 77 | : merger 78 | ? merger(oldValue, newValue, key) 79 | : newValue; 80 | } 81 | return deepMerger; 82 | } 83 | 84 | /** 85 | * It's unclear what the desired behavior is for merging two collections that 86 | * fall into separate categories between keyed, indexed, or set-like, so we only 87 | * consider them mergeable if they fall into the same category. 88 | */ 89 | function areMergeable(oldDataStructure, newDataStructure) { 90 | const oldSeq = Seq(oldDataStructure); 91 | const newSeq = Seq(newDataStructure); 92 | // This logic assumes that a sequence can only fall into one of the three 93 | // categories mentioned above (since there's no `isSetLike()` method). 94 | return ( 95 | isIndexed(oldSeq) === isIndexed(newSeq) && 96 | isKeyed(oldSeq) === isKeyed(newSeq) 97 | ); 98 | } 99 | -------------------------------------------------------------------------------- /__tests__/KeyedSeq.ts: -------------------------------------------------------------------------------- 1 | import { Range, Seq } from 'immutable'; 2 | 3 | import * as jasmineCheck from 'jasmine-check'; 4 | jasmineCheck.install(); 5 | 6 | describe('KeyedSeq', () => { 7 | check.it('it iterates equivalently', [gen.array(gen.int)], ints => { 8 | const seq = Seq(ints); 9 | const keyed = seq.toKeyedSeq(); 10 | 11 | const seqEntries = seq.entries(); 12 | const keyedEntries = keyed.entries(); 13 | 14 | let seqStep, keyedStep; 15 | do { 16 | seqStep = seqEntries.next(); 17 | keyedStep = keyedEntries.next(); 18 | expect(keyedStep).toEqual(seqStep); 19 | } while (!seqStep.done); 20 | }); 21 | 22 | it('maintains keys', () => { 23 | const isEven = x => x % 2 === 0; 24 | const seq = Range(0, 100); 25 | 26 | // This is what we expect for IndexedSequences 27 | const operated = seq.filter(isEven).skip(10).take(5); 28 | expect(operated.entrySeq().toArray()).toEqual([ 29 | [0, 20], 30 | [1, 22], 31 | [2, 24], 32 | [3, 26], 33 | [4, 28], 34 | ]); 35 | const [indexed0, indexed1] = seq 36 | .partition(isEven) 37 | .map(part => part.skip(10).take(5)); 38 | expect(indexed0.entrySeq().toArray()).toEqual([ 39 | [0, 21], 40 | [1, 23], 41 | [2, 25], 42 | [3, 27], 43 | [4, 29], 44 | ]); 45 | expect(indexed1.entrySeq().toArray()).toEqual([ 46 | [0, 20], 47 | [1, 22], 48 | [2, 24], 49 | [3, 26], 50 | [4, 28], 51 | ]); 52 | 53 | // Where Keyed Sequences maintain keys. 54 | const keyed = seq.toKeyedSeq(); 55 | const keyedOperated = keyed.filter(isEven).skip(10).take(5); 56 | expect(keyedOperated.entrySeq().toArray()).toEqual([ 57 | [20, 20], 58 | [22, 22], 59 | [24, 24], 60 | [26, 26], 61 | [28, 28], 62 | ]); 63 | const [keyed0, keyed1] = keyed 64 | .partition(isEven) 65 | .map(part => part.skip(10).take(5)); 66 | expect(keyed0.entrySeq().toArray()).toEqual([ 67 | [21, 21], 68 | [23, 23], 69 | [25, 25], 70 | [27, 27], 71 | [29, 29], 72 | ]); 73 | expect(keyed1.entrySeq().toArray()).toEqual([ 74 | [20, 20], 75 | [22, 22], 76 | [24, 24], 77 | [26, 26], 78 | [28, 28], 79 | ]); 80 | }); 81 | 82 | it('works with reverse', () => { 83 | const seq = Range(0, 100); 84 | 85 | // This is what we expect for IndexedSequences 86 | expect(seq.reverse().take(5).entrySeq().toArray()).toEqual([ 87 | [0, 99], 88 | [1, 98], 89 | [2, 97], 90 | [3, 96], 91 | [4, 95], 92 | ]); 93 | 94 | // Where Keyed Sequences maintain keys. 95 | expect(seq.toKeyedSeq().reverse().take(5).entrySeq().toArray()).toEqual([ 96 | [99, 99], 97 | [98, 98], 98 | [97, 97], 99 | [96, 96], 100 | [95, 95], 101 | ]); 102 | }); 103 | 104 | it('works with double reverse', () => { 105 | const seq = Range(0, 100); 106 | 107 | // This is what we expect for IndexedSequences 108 | expect( 109 | seq.reverse().skip(10).take(5).reverse().entrySeq().toArray() 110 | ).toEqual([ 111 | [0, 85], 112 | [1, 86], 113 | [2, 87], 114 | [3, 88], 115 | [4, 89], 116 | ]); 117 | 118 | // Where Keyed Sequences maintain keys. 119 | expect( 120 | seq.reverse().toKeyedSeq().skip(10).take(5).reverse().entrySeq().toArray() 121 | ).toEqual([ 122 | [14, 85], 123 | [13, 86], 124 | [12, 87], 125 | [11, 88], 126 | [10, 89], 127 | ]); 128 | }); 129 | }); 130 | -------------------------------------------------------------------------------- /perf/Map.js: -------------------------------------------------------------------------------- 1 | describe('Map', function () { 2 | describe('builds from an object', function () { 3 | var obj2 = {}; 4 | for (var ii = 0; ii < 2; ii++) { 5 | obj2['x' + ii] = ii; 6 | } 7 | 8 | it('of 2', function () { 9 | Immutable.Map(obj2); 10 | }); 11 | 12 | var obj8 = {}; 13 | for (var ii = 0; ii < 8; ii++) { 14 | obj8['x' + ii] = ii; 15 | } 16 | 17 | it('of 8', function () { 18 | Immutable.Map(obj8); 19 | }); 20 | 21 | var obj32 = {}; 22 | for (var ii = 0; ii < 32; ii++) { 23 | obj32['x' + ii] = ii; 24 | } 25 | 26 | it('of 32', function () { 27 | Immutable.Map(obj32); 28 | }); 29 | 30 | var obj1024 = {}; 31 | for (var ii = 0; ii < 1024; ii++) { 32 | obj1024['x' + ii] = ii; 33 | } 34 | 35 | it('of 1024', function () { 36 | Immutable.Map(obj1024); 37 | }); 38 | }); 39 | 40 | describe('builds from an array', function () { 41 | var array2 = []; 42 | for (var ii = 0; ii < 2; ii++) { 43 | array2[ii] = ['x' + ii, ii]; 44 | } 45 | 46 | it('of 2', function () { 47 | Immutable.Map(array2); 48 | }); 49 | 50 | var array8 = []; 51 | for (var ii = 0; ii < 8; ii++) { 52 | array8[ii] = ['x' + ii, ii]; 53 | } 54 | 55 | it('of 8', function () { 56 | Immutable.Map(array8); 57 | }); 58 | 59 | var array32 = []; 60 | for (var ii = 0; ii < 32; ii++) { 61 | array32[ii] = ['x' + ii, ii]; 62 | } 63 | 64 | it('of 32', function () { 65 | Immutable.Map(array32); 66 | }); 67 | 68 | var array1024 = []; 69 | for (var ii = 0; ii < 1024; ii++) { 70 | array1024[ii] = ['x' + ii, ii]; 71 | } 72 | 73 | it('of 1024', function () { 74 | Immutable.Map(array1024); 75 | }); 76 | }); 77 | 78 | describe('builds from a List', function () { 79 | var list2 = Immutable.List().asMutable(); 80 | for (var ii = 0; ii < 2; ii++) { 81 | list2 = list2.push(Immutable.List(['x' + ii, ii])); 82 | } 83 | list2 = list2.asImmutable(); 84 | 85 | it('of 2', function () { 86 | Immutable.Map(list2); 87 | }); 88 | 89 | var list8 = Immutable.List().asMutable(); 90 | for (var ii = 0; ii < 8; ii++) { 91 | list8 = list8.push(Immutable.List(['x' + ii, ii])); 92 | } 93 | list8 = list8.asImmutable(); 94 | 95 | it('of 8', function () { 96 | Immutable.Map(list8); 97 | }); 98 | 99 | var list32 = Immutable.List().asMutable(); 100 | for (var ii = 0; ii < 32; ii++) { 101 | list32 = list32.push(Immutable.List(['x' + ii, ii])); 102 | } 103 | list32 = list32.asImmutable(); 104 | 105 | it('of 32', function () { 106 | Immutable.Map(list32); 107 | }); 108 | 109 | var list1024 = Immutable.List().asMutable(); 110 | for (var ii = 0; ii < 1024; ii++) { 111 | list1024 = list1024.push(Immutable.List(['x' + ii, ii])); 112 | } 113 | list1024 = list1024.asImmutable(); 114 | 115 | it('of 1024', function () { 116 | Immutable.Map(list1024); 117 | }); 118 | }); 119 | 120 | describe('merge a map', () => { 121 | [2, 8, 32, 1024].forEach(size => { 122 | const obj1 = {}; 123 | const obj2 = {}; 124 | for (let ii = 0; ii < size; ii++) { 125 | obj1['k' + ii] = '1_' + ii; 126 | obj2['k' + ii] = '2_' + ii; 127 | } 128 | 129 | const map1 = Immutable.Map(obj1); 130 | const map2 = Immutable.Map(obj2); 131 | 132 | it('of ' + size, () => { 133 | map1.merge(map2); 134 | }); 135 | }); 136 | }); 137 | }); 138 | -------------------------------------------------------------------------------- /__tests__/minmax.ts: -------------------------------------------------------------------------------- 1 | import { is, Seq } from 'immutable'; 2 | 3 | import * as jasmineCheck from 'jasmine-check'; 4 | jasmineCheck.install(); 5 | 6 | const genHeterogeneousishArray = gen.oneOf([ 7 | gen.array(gen.oneOf([gen.string, gen.undefined])), 8 | gen.array(gen.oneOf([gen.int, gen.NaN])), 9 | ]); 10 | 11 | describe('max', () => { 12 | it('returns max in a sequence', () => { 13 | expect(Seq([1, 9, 2, 8, 3, 7, 4, 6, 5]).max()).toBe(9); 14 | }); 15 | 16 | it('accepts a comparator', () => { 17 | expect(Seq([1, 9, 2, 8, 3, 7, 4, 6, 5]).max((a, b) => b - a)).toBe(1); 18 | }); 19 | 20 | it('by a mapper', () => { 21 | const family = Seq([ 22 | { name: 'Oakley', age: 7 }, 23 | { name: 'Dakota', age: 7 }, 24 | { name: 'Casey', age: 34 }, 25 | { name: 'Avery', age: 34 }, 26 | ]); 27 | expect(family.maxBy(p => p.age)).toBe(family.get(2)); 28 | }); 29 | 30 | it('by a mapper and a comparator', () => { 31 | const family = Seq([ 32 | { name: 'Oakley', age: 7 }, 33 | { name: 'Dakota', age: 7 }, 34 | { name: 'Casey', age: 34 }, 35 | { name: 'Avery', age: 34 }, 36 | ]); 37 | expect( 38 | family.maxBy( 39 | p => p.age, 40 | (a, b) => b - a 41 | ) 42 | ).toBe(family.get(0)); 43 | }); 44 | 45 | it('surfaces NaN, null, and undefined', () => { 46 | expect(is(NaN, Seq([1, 2, 3, 4, 5, NaN]).max())).toBe(true); 47 | expect(is(NaN, Seq([NaN, 1, 2, 3, 4, 5]).max())).toBe(true); 48 | expect(is(null, Seq(['A', 'B', 'C', 'D', null]).max())).toBe(true); 49 | expect(is(null, Seq([null, 'A', 'B', 'C', 'D']).max())).toBe(true); 50 | }); 51 | 52 | it('null treated as 0 in default iterator', () => { 53 | expect(is(2, Seq([-1, -2, null, 1, 2]).max())).toBe(true); 54 | }); 55 | 56 | check.it('is not dependent on order', [genHeterogeneousishArray], vals => { 57 | expect(is(Seq(shuffle(vals.slice())).max(), Seq(vals).max())).toEqual(true); 58 | }); 59 | }); 60 | 61 | describe('min', () => { 62 | it('returns min in a sequence', () => { 63 | expect(Seq([1, 9, 2, 8, 3, 7, 4, 6, 5]).min()).toBe(1); 64 | }); 65 | 66 | it('accepts a comparator', () => { 67 | expect(Seq([1, 9, 2, 8, 3, 7, 4, 6, 5]).min((a, b) => b - a)).toBe(9); 68 | }); 69 | 70 | it('by a mapper', () => { 71 | const family = Seq([ 72 | { name: 'Oakley', age: 7 }, 73 | { name: 'Dakota', age: 7 }, 74 | { name: 'Casey', age: 34 }, 75 | { name: 'Avery', age: 34 }, 76 | ]); 77 | expect(family.minBy(p => p.age)).toBe(family.get(0)); 78 | }); 79 | 80 | it('by a mapper and a comparator', () => { 81 | const family = Seq([ 82 | { name: 'Oakley', age: 7 }, 83 | { name: 'Dakota', age: 7 }, 84 | { name: 'Casey', age: 34 }, 85 | { name: 'Avery', age: 34 }, 86 | ]); 87 | expect( 88 | family.minBy( 89 | p => p.age, 90 | (a, b) => b - a 91 | ) 92 | ).toBe(family.get(2)); 93 | }); 94 | 95 | check.it('is not dependent on order', [genHeterogeneousishArray], vals => { 96 | expect(is(Seq(shuffle(vals.slice())).min(), Seq(vals).min())).toEqual(true); 97 | }); 98 | }); 99 | 100 | function shuffle(array) { 101 | let m = array.length; 102 | let t; 103 | let i; 104 | 105 | // While there remain elements to shuffle… 106 | while (m) { 107 | // Pick a remaining element… 108 | i = Math.floor(Math.random() * m--); 109 | 110 | // And swap it with the current element. 111 | t = array[m]; 112 | array[m] = array[i]; 113 | array[i] = t; 114 | } 115 | 116 | return array; 117 | } 118 | -------------------------------------------------------------------------------- /__tests__/transformerProtocol.ts: -------------------------------------------------------------------------------- 1 | import * as t from 'transducers-js'; 2 | import { List, Map, Set, Stack } from 'immutable'; 3 | 4 | import * as jasmineCheck from 'jasmine-check'; 5 | jasmineCheck.install(); 6 | 7 | describe('Transformer Protocol', () => { 8 | it('transduces Stack without initial values', () => { 9 | const s = Stack.of(1, 2, 3, 4); 10 | const xform = t.comp( 11 | t.filter(x => x % 2 === 0), 12 | t.map(x => x + 1) 13 | ); 14 | const s2 = t.transduce(xform, Stack(), s); 15 | expect(s.toArray()).toEqual([1, 2, 3, 4]); 16 | expect(s2.toArray()).toEqual([5, 3]); 17 | }); 18 | 19 | it('transduces Stack with initial values', () => { 20 | const v1 = Stack.of(1, 2, 3); 21 | const v2 = Stack.of(4, 5, 6, 7); 22 | const xform = t.comp( 23 | t.filter(x => x % 2 === 0), 24 | t.map(x => x + 1) 25 | ); 26 | const r = t.transduce(xform, Stack(), v1, v2); 27 | expect(v1.toArray()).toEqual([1, 2, 3]); 28 | expect(v2.toArray()).toEqual([4, 5, 6, 7]); 29 | expect(r.toArray()).toEqual([7, 5, 1, 2, 3]); 30 | }); 31 | 32 | it('transduces List without initial values', () => { 33 | const v = List.of(1, 2, 3, 4); 34 | const xform = t.comp( 35 | t.filter(x => x % 2 === 0), 36 | t.map(x => x + 1) 37 | ); 38 | const r = t.transduce(xform, List(), v); 39 | expect(v.toArray()).toEqual([1, 2, 3, 4]); 40 | expect(r.toArray()).toEqual([3, 5]); 41 | }); 42 | 43 | it('transduces List with initial values', () => { 44 | const v1 = List.of(1, 2, 3); 45 | const v2 = List.of(4, 5, 6, 7); 46 | const xform = t.comp( 47 | t.filter(x => x % 2 === 0), 48 | t.map(x => x + 1) 49 | ); 50 | const r = t.transduce(xform, List(), v1, v2); 51 | expect(v1.toArray()).toEqual([1, 2, 3]); 52 | expect(v2.toArray()).toEqual([4, 5, 6, 7]); 53 | expect(r.toArray()).toEqual([1, 2, 3, 5, 7]); 54 | }); 55 | 56 | it('transduces Map without initial values', () => { 57 | const m1 = Map({ a: 1, b: 2, c: 3, d: 4 }); 58 | const xform = t.comp( 59 | t.filter(([k, v]) => v % 2 === 0), 60 | t.map(([k, v]) => [k, v * 2]) 61 | ); 62 | const m2 = t.transduce(xform, Map(), m1); 63 | expect(m1.toObject()).toEqual({ a: 1, b: 2, c: 3, d: 4 }); 64 | expect(m2.toObject()).toEqual({ b: 4, d: 8 }); 65 | }); 66 | 67 | it('transduces Map with initial values', () => { 68 | const m1 = Map({ a: 1, b: 2, c: 3 }); 69 | const m2 = Map({ a: 4, b: 5 }); 70 | const xform = t.comp( 71 | t.filter(([k, v]) => v % 2 === 0), 72 | t.map(([k, v]) => [k, v * 2]) 73 | ); 74 | const m3 = t.transduce(xform, Map(), m1, m2); 75 | expect(m1.toObject()).toEqual({ a: 1, b: 2, c: 3 }); 76 | expect(m2.toObject()).toEqual({ a: 4, b: 5 }); 77 | expect(m3.toObject()).toEqual({ a: 8, b: 2, c: 3 }); 78 | }); 79 | 80 | it('transduces Set without initial values', () => { 81 | const s1 = Set.of(1, 2, 3, 4); 82 | const xform = t.comp( 83 | t.filter(x => x % 2 === 0), 84 | t.map(x => x + 1) 85 | ); 86 | const s2 = t.transduce(xform, Set(), s1); 87 | expect(s1.toArray()).toEqual([1, 2, 3, 4]); 88 | expect(s2.toArray()).toEqual([3, 5]); 89 | }); 90 | 91 | it('transduces Set with initial values', () => { 92 | const s1 = Set.of(1, 2, 3, 4); 93 | const s2 = Set.of(2, 3, 4, 5, 6); 94 | const xform = t.comp( 95 | t.filter(x => x % 2 === 0), 96 | t.map(x => x + 1) 97 | ); 98 | const s3 = t.transduce(xform, Set(), s1, s2); 99 | expect(s1.toArray()).toEqual([1, 2, 3, 4]); 100 | expect(s2.toArray()).toEqual([2, 3, 4, 5, 6]); 101 | expect(s3.toArray()).toEqual([1, 2, 3, 4, 5, 7]); 102 | }); 103 | }); 104 | -------------------------------------------------------------------------------- /website/src/MemberDoc.tsx: -------------------------------------------------------------------------------- 1 | import Link from 'next/link'; 2 | import { Fragment } from 'react'; 3 | import { CallSigDef, MemberDef } from './Defs'; 4 | import { MarkdownContent } from './MarkdownContent'; 5 | import type { MemberDefinition } from './TypeDefs'; 6 | 7 | export function MemberDoc({ member }: { member: MemberDefinition }) { 8 | return ( 9 |
10 |

11 | {member.label} 12 |

13 |
14 | {member.doc && ( 15 | 19 | )} 20 | {!member.signatures ? ( 21 | 22 | 23 | 24 | ) : ( 25 | 26 | {member.signatures.map((callSig, i) => ( 27 | 28 | 29 | {'\n'} 30 | 31 | ))} 32 | 33 | )} 34 | {member.inherited && ( 35 |
36 |

Inherited from

37 | 38 | 39 |
40 | {member.inherited.interface}#{member.inherited.label} 41 | 42 | 43 | 44 |
45 | )} 46 | {member.overrides && ( 47 |
48 |

Overrides

49 | 50 | 51 | 52 | {member.overrides.interface}#{member.overrides.label} 53 | 54 | 55 | 56 |
57 | )} 58 | {member.doc?.notes.map((note, i) => ( 59 |
60 |

{note.name}

61 | {note.name === 'alias' ? ( 62 | 63 | 64 | 65 | ) : ( 66 | 67 | )} 68 |
69 | ))} 70 | {member.doc?.description && ( 71 |
72 |

73 | {member.doc.description.slice(0, 5) === ' 77 | 81 |

82 | )} 83 |
84 |
85 | ); 86 | } 87 | 88 | // export type ParamTypeMap = { [param: string]: Type }; 89 | 90 | // function getParamTypeMap( 91 | // interfaceDef: InterfaceDefinition | undefined, 92 | // member: MemberDefinition 93 | // ): ParamTypeMap | undefined { 94 | // if (!member.inherited || !interfaceDef?.typeParamsMap) return; 95 | // const defining = member.inherited.split('#')[0] + '>'; 96 | // const paramTypeMap: ParamTypeMap = {}; 97 | // // Filter typeParamsMap down to only those relevant to the defining interface. 98 | // for (const [path, type] of Object.entries(interfaceDef.typeParamsMap)) { 99 | // if (path.startsWith(defining)) { 100 | // paramTypeMap[path.slice(defining.length)] = type; 101 | // } 102 | // } 103 | // return paramTypeMap; 104 | // } 105 | -------------------------------------------------------------------------------- /__tests__/flatten.ts: -------------------------------------------------------------------------------- 1 | import { Collection, fromJS, List, Range, Seq } from 'immutable'; 2 | 3 | import * as jasmineCheck from 'jasmine-check'; 4 | jasmineCheck.install(); 5 | 6 | describe('flatten', () => { 7 | it('flattens sequences one level deep', () => { 8 | const nested = fromJS([ 9 | [1, 2], 10 | [3, 4], 11 | [5, 6], 12 | ]); 13 | const flat = nested.flatten(); 14 | expect(flat.toJS()).toEqual([1, 2, 3, 4, 5, 6]); 15 | }); 16 | 17 | it('flattening a List returns a List', () => { 18 | const nested = fromJS([[1], 2, 3, [4, 5, 6]]); 19 | const flat = nested.flatten(); 20 | expect(flat.toString()).toEqual('List [ 1, 2, 3, 4, 5, 6 ]'); 21 | }); 22 | 23 | it('gives the correct iteration count', () => { 24 | const nested = fromJS([ 25 | [1, 2, 3], 26 | [4, 5, 6], 27 | ]); 28 | const flat = nested.flatten(); 29 | expect(flat.forEach((x: any) => x < 4)).toEqual(4); 30 | }); 31 | 32 | type SeqType = number | Array | Collection; 33 | 34 | it('flattens only Sequences (not sequenceables)', () => { 35 | const nested = Seq([Range(1, 3), [3, 4], List([5, 6, 7]), 8]); 36 | const flat = nested.flatten(); 37 | expect(flat.toJS()).toEqual([1, 2, [3, 4], 5, 6, 7, 8]); 38 | }); 39 | 40 | it('can be reversed', () => { 41 | const nested = Seq([Range(1, 3), [3, 4], List([5, 6, 7]), 8]); 42 | const flat = nested.flatten(); 43 | const reversed = flat.reverse(); 44 | expect(reversed.toJS()).toEqual([8, 7, 6, 5, [3, 4], 2, 1]); 45 | }); 46 | 47 | it('can flatten at various levels of depth', () => { 48 | const deeplyNested = fromJS([ 49 | [ 50 | [ 51 | ['A', 'B'], 52 | ['A', 'B'], 53 | ], 54 | [ 55 | ['A', 'B'], 56 | ['A', 'B'], 57 | ], 58 | ], 59 | [ 60 | [ 61 | ['A', 'B'], 62 | ['A', 'B'], 63 | ], 64 | [ 65 | ['A', 'B'], 66 | ['A', 'B'], 67 | ], 68 | ], 69 | ]); 70 | 71 | // deeply flatten 72 | expect(deeplyNested.flatten().toJS()).toEqual([ 73 | 'A', 74 | 'B', 75 | 'A', 76 | 'B', 77 | 'A', 78 | 'B', 79 | 'A', 80 | 'B', 81 | 'A', 82 | 'B', 83 | 'A', 84 | 'B', 85 | 'A', 86 | 'B', 87 | 'A', 88 | 'B', 89 | ]); 90 | 91 | // shallow flatten 92 | expect(deeplyNested.flatten(true).toJS()).toEqual([ 93 | [ 94 | ['A', 'B'], 95 | ['A', 'B'], 96 | ], 97 | [ 98 | ['A', 'B'], 99 | ['A', 'B'], 100 | ], 101 | [ 102 | ['A', 'B'], 103 | ['A', 'B'], 104 | ], 105 | [ 106 | ['A', 'B'], 107 | ['A', 'B'], 108 | ], 109 | ]); 110 | 111 | // flatten two levels 112 | expect(deeplyNested.flatten(2).toJS()).toEqual([ 113 | ['A', 'B'], 114 | ['A', 'B'], 115 | ['A', 'B'], 116 | ['A', 'B'], 117 | ['A', 'B'], 118 | ['A', 'B'], 119 | ['A', 'B'], 120 | ['A', 'B'], 121 | ]); 122 | }); 123 | 124 | describe('flatMap', () => { 125 | it('first maps, then shallow flattens', () => { 126 | const numbers = Range(97, 100); 127 | const letters = numbers.flatMap(v => 128 | fromJS([String.fromCharCode(v), String.fromCharCode(v).toUpperCase()]) 129 | ); 130 | expect(letters.toJS()).toEqual(['a', 'A', 'b', 'B', 'c', 'C']); 131 | }); 132 | 133 | it('maps to sequenceables, not only Sequences.', () => { 134 | const numbers = Range(97, 100); 135 | // the map function returns an Array, rather than a Collection. 136 | // Array is iterable, so this works just fine. 137 | const letters = numbers.flatMap(v => [ 138 | String.fromCharCode(v), 139 | String.fromCharCode(v).toUpperCase(), 140 | ]); 141 | expect(letters.toJS()).toEqual(['a', 'A', 'b', 'B', 'c', 'C']); 142 | }); 143 | }); 144 | }); 145 | -------------------------------------------------------------------------------- /src/Range.js: -------------------------------------------------------------------------------- 1 | import { wrapIndex, wholeSlice, resolveBegin, resolveEnd } from './TrieUtils'; 2 | import { IndexedSeq } from './Seq'; 3 | import { Iterator, iteratorValue, iteratorDone } from './Iterator'; 4 | 5 | import invariant from './utils/invariant'; 6 | import deepEqual from './utils/deepEqual'; 7 | 8 | /** 9 | * Returns a lazy seq of nums from start (inclusive) to end 10 | * (exclusive), by step, where start defaults to 0, step to 1, and end to 11 | * infinity. When start is equal to end, returns empty list. 12 | */ 13 | export class Range extends IndexedSeq { 14 | constructor(start, end, step) { 15 | if (!(this instanceof Range)) { 16 | return new Range(start, end, step); 17 | } 18 | invariant(step !== 0, 'Cannot step a Range by 0'); 19 | start = start || 0; 20 | if (end === undefined) { 21 | end = Infinity; 22 | } 23 | step = step === undefined ? 1 : Math.abs(step); 24 | if (end < start) { 25 | step = -step; 26 | } 27 | this._start = start; 28 | this._end = end; 29 | this._step = step; 30 | this.size = Math.max(0, Math.ceil((end - start) / step - 1) + 1); 31 | if (this.size === 0) { 32 | if (EMPTY_RANGE) { 33 | return EMPTY_RANGE; 34 | } 35 | EMPTY_RANGE = this; 36 | } 37 | } 38 | 39 | toString() { 40 | if (this.size === 0) { 41 | return 'Range []'; 42 | } 43 | return ( 44 | 'Range [ ' + 45 | this._start + 46 | '...' + 47 | this._end + 48 | (this._step !== 1 ? ' by ' + this._step : '') + 49 | ' ]' 50 | ); 51 | } 52 | 53 | get(index, notSetValue) { 54 | return this.has(index) 55 | ? this._start + wrapIndex(this, index) * this._step 56 | : notSetValue; 57 | } 58 | 59 | includes(searchValue) { 60 | const possibleIndex = (searchValue - this._start) / this._step; 61 | return ( 62 | possibleIndex >= 0 && 63 | possibleIndex < this.size && 64 | possibleIndex === Math.floor(possibleIndex) 65 | ); 66 | } 67 | 68 | slice(begin, end) { 69 | if (wholeSlice(begin, end, this.size)) { 70 | return this; 71 | } 72 | begin = resolveBegin(begin, this.size); 73 | end = resolveEnd(end, this.size); 74 | if (end <= begin) { 75 | return new Range(0, 0); 76 | } 77 | return new Range( 78 | this.get(begin, this._end), 79 | this.get(end, this._end), 80 | this._step 81 | ); 82 | } 83 | 84 | indexOf(searchValue) { 85 | const offsetValue = searchValue - this._start; 86 | if (offsetValue % this._step === 0) { 87 | const index = offsetValue / this._step; 88 | if (index >= 0 && index < this.size) { 89 | return index; 90 | } 91 | } 92 | return -1; 93 | } 94 | 95 | lastIndexOf(searchValue) { 96 | return this.indexOf(searchValue); 97 | } 98 | 99 | __iterate(fn, reverse) { 100 | const size = this.size; 101 | const step = this._step; 102 | let value = reverse ? this._start + (size - 1) * step : this._start; 103 | let i = 0; 104 | while (i !== size) { 105 | if (fn(value, reverse ? size - ++i : i++, this) === false) { 106 | break; 107 | } 108 | value += reverse ? -step : step; 109 | } 110 | return i; 111 | } 112 | 113 | __iterator(type, reverse) { 114 | const size = this.size; 115 | const step = this._step; 116 | let value = reverse ? this._start + (size - 1) * step : this._start; 117 | let i = 0; 118 | return new Iterator(() => { 119 | if (i === size) { 120 | return iteratorDone(); 121 | } 122 | const v = value; 123 | value += reverse ? -step : step; 124 | return iteratorValue(type, reverse ? size - ++i : i++, v); 125 | }); 126 | } 127 | 128 | equals(other) { 129 | return other instanceof Range 130 | ? this._start === other._start && 131 | this._end === other._end && 132 | this._step === other._step 133 | : deepEqual(this, other); 134 | } 135 | } 136 | 137 | let EMPTY_RANGE; 138 | -------------------------------------------------------------------------------- /website/src/TypeDefs.ts: -------------------------------------------------------------------------------- 1 | export type TypeDefs = { 2 | version: string; 3 | doc?: TypeDoc; 4 | types: { [name: string]: TypeDefinition }; 5 | }; 6 | 7 | export type TypeDefinition = { 8 | qualifiedName: string; 9 | label: string; // Like a name, but with () for callables. 10 | url: string; 11 | doc?: TypeDoc; 12 | call?: MemberDefinition; 13 | functions?: { [name: string]: MemberDefinition }; 14 | interface?: InterfaceDefinition; 15 | }; 16 | 17 | export type MemberDefinition = { 18 | line: number; 19 | name: string; 20 | label: string; // Like a name, but with () for callables. 21 | url: string; 22 | id: string; // Local reference on a page 23 | group?: string; 24 | doc?: TypeDoc; 25 | isStatic?: boolean; 26 | inherited?: { interface: string; label: string; url: string }; 27 | overrides?: { interface: string; label: string; url: string }; 28 | signatures?: Array; 29 | type?: Type; 30 | }; 31 | 32 | export type CallSignature = { 33 | line?: number; 34 | typeParams?: Array; 35 | params?: Array; 36 | type?: Type; 37 | }; 38 | 39 | export type CallParam = { 40 | name: string; 41 | type: Type; 42 | varArgs?: boolean; 43 | optional?: boolean; 44 | }; 45 | 46 | export type InterfaceDefinition = { 47 | doc?: TypeDoc; 48 | line?: number; 49 | typeParams?: Array; 50 | extends?: Array; 51 | implements?: Array; 52 | members: { [name: string]: MemberDefinition }; 53 | }; 54 | 55 | export type MemberGroup = { 56 | title?: string; 57 | members: { [name: string]: MemberDefinition }; 58 | }; 59 | 60 | export type TypeDoc = { 61 | synopsis: string; 62 | notes: Array; 63 | description: string; 64 | }; 65 | 66 | type TypeDocNote = { name: string; body: string }; 67 | 68 | export enum TypeKind { 69 | Never, 70 | Any, 71 | Unknown, 72 | This, 73 | 74 | Undefined, 75 | Boolean, 76 | Number, 77 | String, 78 | 79 | Object, 80 | Array, 81 | Function, 82 | Param, 83 | Type, 84 | 85 | Union, 86 | Intersection, 87 | Tuple, 88 | Indexed, 89 | Operator, 90 | } 91 | 92 | export type Type = 93 | | NeverType 94 | | AnyType 95 | | UnknownType 96 | | ThisType 97 | | UndefinedType 98 | | BooleanType 99 | | NumberType 100 | | StringType 101 | | UnionType 102 | | IntersectionType 103 | | TupleType 104 | | ObjectType 105 | | ArrayType 106 | | FunctionType 107 | | ParamType 108 | | NamedType 109 | | IndexedType 110 | | OperatorType; 111 | 112 | type NeverType = { k: TypeKind.Never }; 113 | type AnyType = { k: TypeKind.Any }; 114 | type UnknownType = { k: TypeKind.Unknown }; 115 | type ThisType = { k: TypeKind.This }; 116 | 117 | type UndefinedType = { k: TypeKind.Undefined }; 118 | type BooleanType = { k: TypeKind.Boolean }; 119 | type NumberType = { k: TypeKind.Number }; 120 | type StringType = { k: TypeKind.String }; 121 | 122 | type ObjectType = { 123 | k: TypeKind.Object; 124 | members?: Array; 125 | }; 126 | export type ObjectMember = { 127 | index?: boolean; 128 | name?: string; 129 | params?: Array; 130 | type?: Type; 131 | }; 132 | 133 | type ArrayType = { k: TypeKind.Array; type: Type }; 134 | export type FunctionType = { 135 | k: TypeKind.Function; 136 | // Note: does not yet show constraints or defaults 137 | typeParams?: Array; 138 | params: Array; 139 | type: Type; 140 | }; 141 | export type ParamType = { k: TypeKind.Param; param: string }; 142 | export type NamedType = { 143 | k: TypeKind.Type; 144 | name: string; // May be dotted path or other expression 145 | args?: Array; 146 | url?: string; 147 | }; 148 | 149 | type UnionType = { k: TypeKind.Union; types: Array }; 150 | type IntersectionType = { k: TypeKind.Intersection; types: Array }; 151 | type TupleType = { k: TypeKind.Tuple; types: Array }; 152 | type IndexedType = { k: TypeKind.Indexed; type: Type; index: Type }; 153 | type OperatorType = { k: TypeKind.Operator; operator: string; type: Type }; 154 | -------------------------------------------------------------------------------- /website/src/RunkitEmbed.tsx: -------------------------------------------------------------------------------- 1 | import Script from 'next/script'; 2 | 3 | export function RunkitEmbed() { 4 | return