├── .gitignore ├── README.md ├── challenges ├── README.md ├── c#-2 │ ├── DopChallenge.sln │ ├── ReadMe.md │ └── Tests │ │ ├── CatalogModule.cs │ │ ├── DataModel.cs │ │ ├── JsonData.cs │ │ ├── Library_tests.cs │ │ ├── Map.cs │ │ ├── Tests.csproj │ │ └── UserManagementModule.cs ├── c# │ ├── DataOrientedProgramming │ │ ├── Challenge1.cs │ │ ├── Challenge2.cs │ │ ├── Challenges.csproj │ │ ├── DataOrientedProgramming.sln │ │ └── Program.cs │ ├── Models │ │ ├── LibraryData.cs │ │ └── Models.csproj │ └── Tests │ │ ├── Challenge1.cs │ │ ├── Challenge2.cs │ │ └── Tests.csproj ├── clojure-2 │ ├── .gitignore │ ├── deps.edn │ ├── resources │ │ └── lib_data.json │ └── src │ │ └── dop.clj ├── clojure │ ├── core.clj │ ├── libdata.json │ └── project.clj ├── cue │ └── challenges.cue ├── elixir │ ├── .formatter.exs │ ├── .gitignore │ ├── README.md │ ├── lib │ │ ├── block_member.ex │ │ ├── book_property.ex │ │ ├── diff.ex │ │ ├── library_data.ex │ │ ├── merge_and_serialize.ex │ │ ├── rename_keys.ex │ │ └── search_book.ex │ ├── mix.exs │ ├── mix.lock │ └── test │ │ ├── elixir_test.exs │ │ └── test_helper.exs ├── elm │ ├── .gitignore │ ├── README.md │ ├── elm.json │ └── src │ │ ├── Detailed.elm │ │ └── Minimal.elm ├── golang │ ├── challenges.go │ ├── godash.go │ ├── library_data.go │ └── main.go ├── javascript │ ├── block-member.js │ ├── book-property.js │ ├── diff.js │ ├── library-data.js │ ├── merge-and-serialize.js │ ├── rename-keys.js │ └── search-book.js ├── lms-data-diagram.png ├── lms-data-mindmap.png ├── python-2 │ ├── .gitignore │ ├── challenges.py │ ├── data.py │ ├── requirements.txt │ └── tests.py ├── python │ ├── .gitignore │ ├── add_information.py │ ├── compare_data.py │ ├── library_data.py │ ├── merge_information.py │ ├── rename_keys.py │ ├── retrieve_information.py │ └── search_information.py ├── ruby │ ├── block-member.rb │ ├── book-property.rb │ ├── diff.rb │ ├── library-data.rb │ ├── merge-and-serialize.rb │ ├── rename-keys.rb │ └── search-book.rb └── typescript │ ├── README.md │ ├── package.json │ ├── src │ ├── block-member.ts │ ├── data-diff.ts │ ├── data-model.ts │ ├── get-book-property.ts │ ├── merge-info.ts │ ├── rename-keys.ts │ ├── search-books.ts │ ├── test-data.ts │ └── utils.ts │ └── tsconfig.json ├── community └── README.md └── src ├── appendix-a ├── add-author-request-schema.js ├── add-on-the-fly.js ├── author-data.cs ├── author-data.java ├── author-info-refactored.js ├── author-info.js ├── author-invalid-books.js ├── author-no-books.js ├── author-schema-complex.js ├── author-schema-visualize.json ├── author-schema.js ├── breaking-1-fp.js ├── breaking-1-oo.js ├── breaking-2-oo.js ├── check-data-validity.js ├── data-validation-errors.js ├── duplicating-oo.js ├── following-1-fp.js ├── following-1-oo.js ├── following-2-literal.js ├── following-2-oo.js ├── full-name-oo.js ├── is-prolific.cs ├── is-prolific.java ├── manipulate-generic.js ├── mutating-in-place.js ├── mutating-via-cloning.js ├── mutating-via-immutable.js ├── not-conforming.js ├── receive-data.js ├── serialize-free.js ├── testing-full-oo.js ├── testing-isolated-fp.js ├── testing-isolated-oo.js ├── unpredictable-async.js ├── using-same-fp.js └── using-same-oo.js ├── appendix-b ├── book-class.java ├── book-nested-class.java ├── book-record.java ├── dynamic-access-class-title.java ├── dynamic-access-class.java ├── dynamic-access-map.java ├── dynamic-access-mapping.java ├── dynamic-access-nested-class-language.java ├── dynamic-access-nested-class.java ├── dynamic-access-nested-map.java ├── get-casting-nested.java ├── get-casting.java ├── get-value-getter-nested.java ├── gson-example-nested.java ├── gson-example-search-results.java ├── gson-example.java ├── search-results-map.java ├── search-results-records.java ├── seven-nested-record.java ├── typed-getter-impl.java ├── typed-getter-mapping-objects.java ├── typed-getter-mapping.java ├── typed-getter-nested-impl.java ├── typed-getter-nested-title.java ├── typed-getter-title.java ├── value-getter-class-mapping.java ├── value-getter-class-title.java ├── value-getter-map-nested.java ├── value-getter-map.java ├── value-getter-mapping.java ├── value-getter-nested-class-title.java ├── value-getter-title-map.java ├── watchmen-map.java ├── watchmen-record.java └── watchmen-title-reflection.java ├── appendix-d └── lodash-config.js ├── chapter01 ├── apparently-simple.js ├── really-simple.js ├── search-input.json └── search-output.json ├── chapter02 ├── add-book-item-signature.js ├── add-book-item-vip.js ├── get-book-lending-oo-python.py ├── get-book-lendings-do.js ├── get-book-lendings-library.js ├── get-book-lendings-oo.js └── get-book-lendings-super.js ├── chapter03 ├── author-names.js ├── book-info.js ├── catalog-data.js ├── custom-map-test.js ├── filter-impl-test.js ├── filter_impl.js ├── get-impl-test.js ├── get_impl.js ├── is-librarian-test.js ├── is-librarian.js ├── is-vip.js ├── library-data-catalog.js ├── map-author-names.js ├── map_impl.js ├── search-book-by-title.js ├── search-book-json-test.js ├── search-book-json.js ├── search-books-test.js ├── search-watchmen.js ├── search.js ├── user-management-data.js ├── user-management.js ├── watchmen-book-path.js ├── watchmen-do.java ├── watchmen-do.js ├── watchmen-oo.js ├── watchmen-search-result.js ├── watchmen-title.java └── watchmen.js ├── chapter04 ├── add-member-library.js ├── add-member-system.js ├── add-member.js ├── lodash-config.js ├── modify-shared-data.js ├── structural-sharing-impl.js ├── system-data-memo.js ├── system-data-single-thread.js ├── update-publication-year.js └── validate.js ├── chapter05 ├── ax1.json ├── ax1y2.json ├── ax2.json ├── ay2.json ├── consistency.js ├── data-diff-diff.js ├── data-diff-example.js ├── diff-previous-current.js ├── diff-previous-next.js ├── leaves-previous-current.js ├── leaves-previous-next.js ├── leaves.js ├── library-data.js ├── patch.js ├── paths-in-common.js ├── reduce_impl.js ├── reduce_numbers.js ├── system-data-single-thread.js ├── test-reduce.js └── three-way-merge-example.js ├── chapter06 ├── add-member.js ├── author-names-more-tests.js ├── author-names-test.js ├── author-names.js ├── book-info-pass-simple.js ├── book-info.js ├── catalog-data-min.js ├── catalog-data.js ├── catalog-search-lower.js ├── catalog-search-test.js ├── catalog-search.js ├── fix-search-book.js ├── is-equal-2.js ├── is-equal.js ├── library-search.js ├── search-lib-test.js ├── search.js ├── string-deserialize.js ├── system-add-member.js ├── system-state.js ├── test-add-member-empty.js ├── test-add-member-error.js ├── test-add-member-initial-data.js ├── test-case.js ├── test-lib-add-member.js ├── test-system-add-member.js └── two-strings-same-data.js ├── chapter07 ├── book-info-basic-schema.json ├── book-info-example.json ├── book-info-schema-example.js ├── book-info-schema.js ├── books-example.json ├── books-schema-2.js ├── books-schema.json ├── global-validate.js ├── invalid-search-books.js ├── json-schema-super-example.json ├── map-schema-skeleton.json ├── search-books-error-readable.js ├── search-books-errors.js ├── search-books-multiple-errors.js ├── search-books-request-schema.js ├── search-books-response-schema-sep.js ├── search-books-response-schema.js ├── search-books-validation-failures.json ├── search-request-schema-skeleton-2.json ├── search-request-schema-skeleton-3.json ├── search-request-schema-skeleton.json ├── search-request.json ├── search-response.json ├── super-example.json └── valid-search-books.js ├── chapter08 ├── atom.java ├── atom.js ├── cache-with-atom.js ├── cache-with-lock.js ├── counter-with-atom.java ├── counter-with-atom.js ├── counter-with-lock.js ├── system-data-commit.js ├── system-data-single-thread.js └── system-data.js ├── chapter09 ├── add-member.js ├── assoc.java ├── data-diff.js ├── deep-freeze.js ├── foreach.java ├── freeze-object.js ├── get-in.js ├── immutable-lodash-diff.js ├── immutable-lodash-json.js ├── immutable-lodash-search.js ├── immutable-stringify.js ├── library-data.js ├── make-immutable-list.java ├── make-immutable-map.java ├── replace.java ├── search.js ├── set-in.js ├── stream.java ├── test-add-member.js ├── test-data-diff.js └── test-search.js ├── chapter10 ├── add-member-0.js ├── add-member.js ├── add-member.sql ├── aggregate-field.js ├── aggregate-one-field.js ├── aggregate-rows.js ├── aggregate.js ├── at.js ├── db-search-schema.js ├── group-by-isbn.js ├── jdbc.java ├── json-schema-cheatsheet.json ├── rename-fields.js ├── rename-result-keys-test.js ├── rename-result-keys.js ├── rename-specific-keys.js ├── rename-user-info.js ├── rows-by-isbn.json ├── search-join.sql ├── search-results-extended.json ├── search-simple-rename.sql ├── search-simple.js ├── search-simple.sql ├── sql-results.json ├── sql-simple-results.json └── test-aggregate-one-field.js ├── chapter11 ├── api-response-schema.js ├── catalog-enrich.js ├── enrich-book-info-db.js ├── enrich-book-info-open-api-schema.js ├── enrich-book-info-open-api.js ├── enrich-book-info-schema.js ├── enrich-book-info.js ├── fetch-7-habits.js ├── fetch-and-log.js ├── fetch-open-lib.js ├── important-fields.txt ├── join-arrays-test.js ├── join-arrays.js ├── key-by-result.json ├── key-by.js ├── matching-books.js ├── merge-data.js ├── multiple-book-info.js ├── parse.java ├── parse.js ├── schema-response.js ├── search-json.json ├── search-request-schema.js ├── seven-habits-db.js ├── seven-habits-open-api.js └── seven-habits-open-api.json ├── chapter12 ├── author-schema.js ├── book-schema.js ├── catalog-generated-data.json ├── catalog-generated.txt ├── catalog-schema-part-2.js ├── catalog-schema-simple.js ├── catalog-schema.js ├── date-schema.json ├── json-schema-super-example-revised.json ├── number-map.json ├── publication-year-schema.js ├── run-test.js ├── search-again.js ├── search-auto-test-1.js ├── search-auto-test-2.js ├── search-auto-test-catch.js ├── search-auto-test.js ├── search-books-arg-schema.js ├── search-fixed.js ├── search-return-schema.js ├── search-with-input-validation.js ├── search-without-validation.js ├── search.js ├── super-example-revised.json ├── tuple.json ├── unit-test-output.js ├── unit-test-pass.js ├── uuid-random.js ├── uuid-regex.txt └── uuid-schema.js ├── chapter13 ├── animal-dispatch.js ├── animal-functions.js ├── animal-lang-dispatch.js ├── animal-lang-methods.js ├── animal-oop.java ├── animal-presentation.js ├── animal-switch-validate.js ├── animal-switch.js ├── author-html.html ├── author-info-dispatch.js ├── author-markdown.md ├── author-name-html-methods.js ├── author-name-markdown-methods.js ├── author-name-test-markdown.js ├── author-names-test.js ├── author-schema.js ├── dys-greet-all.js ├── dys-greet-dispatch.js ├── dys-greet-methods.js ├── format-schema.js ├── greet-cat.js ├── greet-cow-call.js ├── greet-cow.js ├── greet-default.js ├── greet-dog-french.js ├── greet-dog.js ├── greet-horse.js ├── language-schema.js └── languages.js ├── chapter14 ├── array-of-arrays.js ├── author-ids-0.js ├── author-ids-1.js ├── author-ids-2.js ├── books-by-lib-unwind.js ├── books-in-libs-test.js ├── count-by-bool-field-test.js ├── count-by-bool-field.js ├── flat-map.js ├── lending-ratio-0.js ├── lending-ratio-1.js ├── lending-ratio.js ├── list-books.js ├── remove-duplicates-0.js ├── remove-duplicates.js ├── unwind-test.js ├── unwind.js ├── update-inc.js └── update.js └── chapter15 ├── .#catalog-data.js ├── data-file-name.js ├── digit-capture.js ├── digit.js ├── dump-data-ident.js ├── dump-data.js ├── prefix-capture-json.js ├── prefix-capture.js ├── prefix-improved.js ├── prefix-run-1.js ├── prefix.js ├── read-data.js ├── search-capture-log-ident.json ├── search-capture-log.json ├── search-capture-log.txt ├── search-capture.js ├── search-dump.js ├── search-improved.js ├── search-reproduce.js ├── search-test-1.js ├── search-test-2.js ├── search-test-multiple-fail.js ├── search-test-multiple.js ├── search-test-paste.js ├── search-test.js ├── serialize-string.js └── watch-output.txt /.gitignore: -------------------------------------------------------------------------------- 1 | bin/ 2 | obj/ 3 | .idea/ 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Data-Oriented programming 2 | 3 | Source code for [Data Oriented Programming](https://www.manning.com/books/data-oriented-programming?utm_source=viebel&utm_medium=affiliate&utm_campaign=book_sharvit2_data_1_29_21&a_aid=viebel&a_bid=d5b546b7) book 4 | 5 | 6 | # Other implementations 7 | 8 | Have you implemented part of the tasks mentioned in the book in a programming language that you like? Open a PR with a link to your repository! 9 | 10 | - [Typescript](https://github.com/shybyte/data-oriented-programming-book-experiments) by shybyte 11 | - [Java and Paguro](https://github.com/GlenKPeterson/DataOrientedExamples) by GlenKPeterson 12 | -------------------------------------------------------------------------------- /challenges/c#-2/DopChallenge.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tests", "Tests\Tests.csproj", "{C899F94E-CCAD-4CEC-8A3E-6E4115D7DEB6}" 4 | EndProject 5 | Global 6 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 7 | Debug|Any CPU = Debug|Any CPU 8 | Release|Any CPU = Release|Any CPU 9 | EndGlobalSection 10 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 11 | {C899F94E-CCAD-4CEC-8A3E-6E4115D7DEB6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 12 | {C899F94E-CCAD-4CEC-8A3E-6E4115D7DEB6}.Debug|Any CPU.Build.0 = Debug|Any CPU 13 | {C899F94E-CCAD-4CEC-8A3E-6E4115D7DEB6}.Release|Any CPU.ActiveCfg = Release|Any CPU 14 | {C899F94E-CCAD-4CEC-8A3E-6E4115D7DEB6}.Release|Any CPU.Build.0 = Release|Any CPU 15 | EndGlobalSection 16 | EndGlobal 17 | -------------------------------------------------------------------------------- /challenges/c#-2/Tests/DataModel.cs: -------------------------------------------------------------------------------- 1 | namespace Tests; 2 | 3 | record LibraryData(CatalogData Catalog, UserManagementData UserManagement); 4 | 5 | record UserManagementData( 6 | Map Members, 7 | Map Librarians); 8 | 9 | record LibrarianData; 10 | 11 | record MemberData(bool IsBlocked); 12 | 13 | record UserData; 14 | 15 | record CatalogData( 16 | Map BooksByIsbn, 17 | Map AuthorsById); 18 | 19 | record BookItemData; 20 | 21 | record AuthorData(string Name, string[] BookIsbns); 22 | 23 | record BookData( 24 | string Isbn, 25 | string Title, 26 | int PublicationYear, 27 | BookItemData[] BookItems, 28 | string[] AuthorIds); -------------------------------------------------------------------------------- /challenges/c#-2/Tests/Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net6.0 5 | enable 6 | 7 | false 8 | 9 | 10 | 11 | 12 | 13 | 14 | runtime; build; native; contentfiles; analyzers; buildtransitive 15 | all 16 | 17 | 18 | runtime; build; native; contentfiles; analyzers; buildtransitive 19 | all 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /challenges/c#-2/Tests/UserManagementModule.cs: -------------------------------------------------------------------------------- 1 | namespace Tests; 2 | 3 | static class UserManagementModule 4 | { 5 | public static MemberData BlockMember(this UserManagementData m, string email) => 6 | FindMember(m, email) with 7 | { 8 | IsBlocked = true 9 | }; 10 | 11 | public static UserManagementData UpdateMember( 12 | this UserManagementData m, 13 | string email, 14 | MemberData d) => m with 15 | { 16 | Members = m.Members.With(email, d) 17 | }; 18 | 19 | public static MemberData FindMember(this UserManagementData m, string email) => 20 | m.Members[email]; 21 | } -------------------------------------------------------------------------------- /challenges/c#/DataOrientedProgramming/Challenges.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net6.0 6 | enable 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /challenges/c#/DataOrientedProgramming/Program.cs: -------------------------------------------------------------------------------- 1 | Console.WriteLine("Data Oriented Programming - C#"); -------------------------------------------------------------------------------- /challenges/c#/Models/Models.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net6.0 5 | enable 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /challenges/c#/Tests/Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net6.0 5 | enable 6 | 7 | false 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /challenges/clojure-2/.gitignore: -------------------------------------------------------------------------------- 1 | .cpcache/ 2 | -------------------------------------------------------------------------------- /challenges/clojure-2/deps.edn: -------------------------------------------------------------------------------- 1 | {:deps {org.clojure/clojure {:mvn/version "1.11.0-alpha3"} 2 | org.clojure/data.json {:mvn/version "2.4.0"}}} 3 | -------------------------------------------------------------------------------- /challenges/clojure/project.clj: -------------------------------------------------------------------------------- 1 | (defproject dop "0.1.0-SNAPSHOT" 2 | :description "data-oriented programming challenge" 3 | :url "https://github.com/mchampine/data-oriented-programming" 4 | :license {:name "EPL-2.0 OR GPL-2.0-or-later WITH Classpath-exception-2.0" 5 | :url "https://www.eclipse.org/legal/epl-2.0/"} 6 | :dependencies [[org.clojure/clojure "1.10.1"] 7 | [org.clojure/data.json "2.0.2"]] 8 | :repl-options {:init-ns dop.core}) 9 | -------------------------------------------------------------------------------- /challenges/elixir/.formatter.exs: -------------------------------------------------------------------------------- 1 | # Used by "mix format" 2 | [ 3 | inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"] 4 | ] 5 | -------------------------------------------------------------------------------- /challenges/elixir/.gitignore: -------------------------------------------------------------------------------- 1 | # The directory Mix will write compiled artifacts to. 2 | /_build/ 3 | 4 | # If you run "mix test --cover", coverage assets end up here. 5 | /cover/ 6 | 7 | # The directory Mix downloads your dependencies sources to. 8 | /deps/ 9 | 10 | # Where third-party dependencies like ExDoc output generated docs. 11 | /doc/ 12 | 13 | # Ignore .fetch files in case you like to edit your project deps locally. 14 | /.fetch 15 | 16 | # If the VM crashes, it generates a dump, let's ignore it too. 17 | erl_crash.dump 18 | 19 | # Also ignore archive artifacts (built via "mix archive.build"). 20 | *.ez 21 | 22 | # Ignore package tarball (built via "mix hex.build"). 23 | elixir-*.tar 24 | 25 | 26 | # Temporary files for e.g. tests 27 | /tmp 28 | -------------------------------------------------------------------------------- /challenges/elixir/README.md: -------------------------------------------------------------------------------- 1 | # Elixir DOP-Challenges 2 | 3 | [Elixir](https://elixir-lang.org) solutions for the [DOP-Challenges](https://blog.klipse.tech/dop/2021/04/01/dop-challenges.html). 4 | 5 | ## Installation 6 | 7 | [Install elixir](https://elixir-lang.org/install.html) 8 | 9 | run `mix deps.get`. 10 | 11 | ## Tests 12 | 13 | There are [doctests](https://elixir-lang.org/getting-started/mix-otp/docs-tests-and-with.html) written for all the modules. 14 | 15 | run `mix test` to run the whole test suite. 16 | -------------------------------------------------------------------------------- /challenges/elixir/lib/block_member.ex: -------------------------------------------------------------------------------- 1 | defmodule BlockMember do 2 | @doc """ 3 | A function that receives library data and an email address and returns a new version 4 | of library data without altering the original version, where the user with the given email is blocked. 5 | 6 | ## Examples 7 | 8 | iex> updated_library_data = BlockMember.block_member(LibraryData.library_data(), "samantha@gmail.com") 9 | iex> information_path = ["userManagement", "members", "samantha@gmail.com", "isBlocked"] 10 | iex> [get_in(updated_library_data, information_path), get_in(LibraryData.library_data(), information_path)] 11 | [true, false] 12 | """ 13 | def block_member(library_data, email) do 14 | information_path = ["userManagement", "members", email, "isBlocked"] 15 | put_in(library_data, information_path, true) 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /challenges/elixir/lib/book_property.ex: -------------------------------------------------------------------------------- 1 | defmodule BookProperty do 2 | @doc """ 3 | A function that receives library data and ISBN and a field name and returns the value of the field for the book with the given ISBN 4 | 5 | ## Examples 6 | 7 | iex> BookProperty.get_book_property(LibraryData.library_data(), "978-1779501127", "title") |> String.upcase() 8 | "WATCHMEN" 9 | """ 10 | def get_book_property(library_data, isbn, field_name) do 11 | information_path = ["catalog", "booksByIsbn", isbn, field_name] 12 | get_in(library_data, information_path) 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /challenges/elixir/lib/rename_keys.ex: -------------------------------------------------------------------------------- 1 | defmodule RenameKeys do 2 | @doc """ 3 | A function that receives a data entity and a key mappings and returns a new data entity, 4 | without altering the original entity, where the fields are renamed according to the key mappings. 5 | 6 | ## Examples 7 | 8 | iex> alan_moore = %{"name" => "Alan Moore", "bookIsbns" => ["978-1779501127"]} 9 | iex> RenameKeys.rename_keys(alan_moore, %{"bookIsbns" => "books"}) 10 | %{"books" => ["978-1779501127"], "name" => "Alan Moore"} 11 | 12 | iex> book_item = %{"id" => "book-item-1", "rackId" => "rack-17", "isLent" => true} 13 | iex> RenameKeys.rename_keys(book_item, %{"rackId" => "id", "id" => "bookItemId"}) 14 | %{"bookItemId" => "book-item-1", "id" => "rack-17", "isLent" => true} 15 | """ 16 | def rename_keys(map, key_map) do 17 | Enum.reduce(key_map, map, fn {old_key, new_key}, acc -> 18 | with({v, m} <- Map.pop(acc, old_key), do: Map.put(m, new_key, v)) 19 | end) 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /challenges/elixir/mix.exs: -------------------------------------------------------------------------------- 1 | defmodule Elixir.MixProject do 2 | use Mix.Project 3 | 4 | def project do 5 | [ 6 | app: :elixir, 7 | version: "0.1.0", 8 | elixir: "~> 1.11", 9 | start_permanent: Mix.env() == :prod, 10 | deps: deps() 11 | ] 12 | end 13 | 14 | # Run "mix help compile.app" to learn about applications. 15 | def application do 16 | [ 17 | extra_applications: [:logger] 18 | ] 19 | end 20 | 21 | # Run "mix help deps" to learn about dependencies. 22 | defp deps do 23 | [ 24 | {:jason, "~> 1.2"} 25 | # {:dep_from_hexpm, "~> 0.3.0"}, 26 | # {:dep_from_git, git: "https://github.com/elixir-lang/my_dep.git", tag: "0.1.0"} 27 | ] 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /challenges/elixir/mix.lock: -------------------------------------------------------------------------------- 1 | %{ 2 | "jason": {:hex, :jason, "1.2.2", "ba43e3f2709fd1aa1dce90aaabfd039d000469c05c56f0b8e31978e03fa39052", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "18a228f5f0058ee183f29f9eae0805c6e59d61c3b006760668d8d18ff0d12179"}, 3 | } 4 | -------------------------------------------------------------------------------- /challenges/elixir/test/elixir_test.exs: -------------------------------------------------------------------------------- 1 | defmodule ElixirTest do 2 | use ExUnit.Case 3 | doctest BlockMember 4 | doctest BookProperty 5 | doctest Diff 6 | doctest MergeAndSerialize 7 | doctest RenameKeys 8 | doctest SearchBook 9 | end 10 | -------------------------------------------------------------------------------- /challenges/elixir/test/test_helper.exs: -------------------------------------------------------------------------------- 1 | ExUnit.start() 2 | -------------------------------------------------------------------------------- /challenges/elm/.gitignore: -------------------------------------------------------------------------------- 1 | # Elm 2 | elm-stuff -------------------------------------------------------------------------------- /challenges/elm/README.md: -------------------------------------------------------------------------------- 1 | # Data Oriented Programming Challenges - in Elm 2 | 3 | A Data Oriented Programming (DOP) challenge based on https://blog.klipse.tech/dop/2021/04/01/dop-challenges.html 4 | 5 | For this challenge I'm using the package [json-tools/json-value](https://package.elm-lang.org/packages/json-tools/json-value/latest) for basic JSON manipulation. The only other non-core package is [elm-community/result-extra](https://package.elm-lang.org/packages/elm-community/result-extra/latest/) which is used to simplify a couple `List (Result e a)` into `Result e (List a)`. 6 | 7 | For all of these answers there's an assumption that the consumer of them has already decoded the JSON into a JsonValue type. 8 | 9 | There are also 2 versions of the solutions. A [detailed](./src/Detailed.elm) solution that doesn't use any function composition (`>>`) and types out every argument. The other is a [minimal](./src/Minimal.elm) solution that does use function composition (`>>`) where appropriate as well as not typing out arguments when allowed. There are comments near each to describe what's going on. 10 | 11 | - written by [wolfadex](https://github.com/wolfadex/) 12 | -------------------------------------------------------------------------------- /challenges/elm/elm.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "application", 3 | "source-directories": [ 4 | "src" 5 | ], 6 | "elm-version": "0.19.1", 7 | "dependencies": { 8 | "direct": { 9 | "elm/browser": "1.0.2", 10 | "elm/core": "1.0.5", 11 | "elm/html": "1.0.0", 12 | "elm/json": "1.1.3", 13 | "elm-community/result-extra": "2.4.0", 14 | "json-tools/json-value": "1.0.1" 15 | }, 16 | "indirect": { 17 | "elm/time": "1.0.0", 18 | "elm/url": "1.0.0", 19 | "elm/virtual-dom": "1.0.2" 20 | } 21 | }, 22 | "test-dependencies": { 23 | "direct": {}, 24 | "indirect": {} 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /challenges/javascript/block-member.js: -------------------------------------------------------------------------------- 1 | function blockMember(libraryData, email) { 2 | var informationPath = ["userManagement", "members", email, "isBlocked"]; 3 | return _.set(libraryData, informationPath, true); 4 | } 5 | 6 | var updatedLibraryData = blockMember(libraryData, "samantha@gmail.com"); 7 | 8 | var informationPath = ["userManagement", "members", "samantha@gmail.com", "isBlocked"]; 9 | [_.get(updatedLibraryData, informationPath), _.get(libraryData, informationPath)]; 10 | 11 | 12 | -------------------------------------------------------------------------------- /challenges/javascript/book-property.js: -------------------------------------------------------------------------------- 1 | function getBookProperty(libraryData, isbn, fieldName) { 2 | var informationPath = ["catalog", "booksByIsbn", isbn, fieldName]; 3 | return _.get(libraryData, informationPath); 4 | } 5 | 6 | getBookProperty(libraryData, "978-1779501127", "title"); 7 | -------------------------------------------------------------------------------- /challenges/javascript/diff.js: -------------------------------------------------------------------------------- 1 | function diffObjects(data1, data2) { 2 | var emptyObject = _.isArray(data1) ? [] : {}; 3 | if(data1 == data2) { 4 | return emptyObject; 5 | } 6 | var keys = _.union(_.keys(data1), _.keys(data2)); 7 | return _.reduce(keys, 8 | function (acc, k) { 9 | var res = diff(_.get(data1, k), 10 | _.get(data2, k)); 11 | if((_.isObject(res) && _.isEmpty(res)) || 12 | (res == "data-diff:no-diff")) { 13 | return acc; 14 | } 15 | return _.set(acc, [k], res); 16 | }, 17 | emptyObject); 18 | } 19 | 20 | function diff(data1, data2) { 21 | if(_.isObject(data1) && _.isObject(data2)) { 22 | return diffObjects(data1, data2); 23 | } 24 | if(data1 !== data2) { 25 | return data2; 26 | } 27 | return "data-diff:no-diff"; 28 | } 29 | 30 | diff(libraryData, updatedLibraryData); 31 | 32 | diff(libraryData, libraryDataupdatedLibraryData); 33 | 34 | -------------------------------------------------------------------------------- /challenges/javascript/rename-keys.js: -------------------------------------------------------------------------------- 1 | function renameKeys(map, keyMap) { 2 | return _.reduce(keyMap, 3 | function(res, newKey, oldKey) { 4 | var value = _.get(map, oldKey); 5 | var resWithNewKey = _.set(res, newKey, value); 6 | var resWithoutOldKey = _.omit(resWithNewKey, oldKey); 7 | return resWithoutOldKey; 8 | }, 9 | map); 10 | } 11 | ~~~ 12 | 13 | var alanMoore = { 14 | "name": "Alan Moore", 15 | "bookIsbns": ["978-1779501127"] 16 | }; 17 | renameKeys(alanMoore, {"bookIsbns": "books"}); 18 | 19 | 20 | 21 | var bookItem = { 22 | "id": "book-item-1", 23 | "rackId": "rack-17", 24 | "isLent": true 25 | }; 26 | 27 | renameKeys(bookItem, {"rackId": "id", 28 | "id": "bookItemId"}); 29 | -------------------------------------------------------------------------------- /challenges/javascript/search-book.js: -------------------------------------------------------------------------------- 1 | function authorNames(catalogData, book) { 2 | return _.map(_.get(book, "authorIds"), 3 | function(authorId) { 4 | return _.get(catalogData, ["authorsById", authorId, "name"]); 5 | }); 6 | } 7 | 8 | function bookInfo(catalogData, book) { 9 | return { 10 | "title": _.get(book, "title"), 11 | "isbn": _.get(book, "isbn"), 12 | "authorNames": authorNames(catalogData, book) 13 | }; 14 | } 15 | 16 | function searchBooksByTitle(libraryData, query) { 17 | var catalogData = _.get(libraryData, "catalog"); 18 | var allBooks = _.get(catalogData, "booksByIsbn"); 19 | var matchingBooks = _.filter(allBooks, function(book) { 20 | return _.get(book, "title").toLowerCase() 21 | .includes(query.toLowerCase()); 22 | }); 23 | return JSON.stringify(_.map(matchingBooks, function(book) { 24 | return bookInfo(catalogData, book); 25 | })); 26 | } 27 | 28 | searchBooksByTitle(libraryData, "watCH"); 29 | 30 | -------------------------------------------------------------------------------- /challenges/lms-data-diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/viebel/data-oriented-programming/32507650af143b22d87bf8408f93fd9d94b8e043/challenges/lms-data-diagram.png -------------------------------------------------------------------------------- /challenges/lms-data-mindmap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/viebel/data-oriented-programming/32507650af143b22d87bf8408f93fd9d94b8e043/challenges/lms-data-mindmap.png -------------------------------------------------------------------------------- /challenges/python-2/.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__/ 2 | -------------------------------------------------------------------------------- /challenges/python-2/requirements.txt: -------------------------------------------------------------------------------- 1 | deepdiff==5.7.0 2 | mergedeep==1.3.4 3 | ordered-set==4.0.2 4 | pydash==5.1.0 5 | toolz==0.11.2 6 | -------------------------------------------------------------------------------- /challenges/python/.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | __pycache__/ 3 | -------------------------------------------------------------------------------- /challenges/python/add_information.py: -------------------------------------------------------------------------------- 1 | def block_member(library_data, email): 2 | return __set_in( 3 | library_data, 4 | ['userManagement', 'members', email, 'isBlocked'], 5 | True) 6 | 7 | 8 | def __set_in(m, ks, v): 9 | updated_m = m.copy() 10 | updated_m[ks[0]] = v if len(ks) == 1 else __set_in(m[ks[0]], ks[1:], v) 11 | return updated_m 12 | 13 | 14 | def is_blocked(data, email): 15 | return data['userManagement']['members'][email]['isBlocked'] 16 | 17 | 18 | if __name__ == '__main__': 19 | from library_data import data 20 | 21 | email = 'samantha@gmail.com' 22 | updated = block_member(data, email) 23 | 24 | print(f'before: {is_blocked(data, email)} after: {is_blocked(updated, email)}') 25 | -------------------------------------------------------------------------------- /challenges/python/compare_data.py: -------------------------------------------------------------------------------- 1 | def is_not_container(v): 2 | return not(isinstance(v, dict) or isinstance(v, list)) 3 | 4 | 5 | def diff_objects(data1, data2): 6 | if data1 == data2: 7 | return None 8 | if is_not_container(data2): 9 | return data2 10 | elif len(data1) == 0: 11 | return data2 12 | else: 13 | keys = set(set(data1.keys()) | set(data2.keys())) 14 | result = {} 15 | 16 | for k in keys: 17 | diff = diff_objects(data1[k], data2[k]) 18 | 19 | if diff is not None: 20 | result[k] = diff 21 | 22 | if len(result) == 0: 23 | return None 24 | else: 25 | return result 26 | 27 | 28 | if __name__ == '__main__': 29 | from library_data import data 30 | from add_information import block_member 31 | 32 | print(diff_objects(data, data)) 33 | 34 | updated = block_member(data, 'samantha@gmail.com') 35 | 36 | print(diff_objects(data, updated)) 37 | 38 | -------------------------------------------------------------------------------- /challenges/python/rename_keys.py: -------------------------------------------------------------------------------- 1 | def rename_keys(m, key_map): 2 | return {key_to_use(key_map, k): v for (k, v) in m.items()} 3 | 4 | 5 | def key_to_use(key_map, k): 6 | return key_map[k] if k in key_map else k 7 | 8 | 9 | def show(original, updated): 10 | print(f'original:{original}') 11 | print(f'updated:{updated}\n') 12 | 13 | 14 | if __name__ == '__main__': 15 | alan_moore = { 16 | 'name': 'Alan Moore', 17 | 'bookIsbns': ['978-1779501127']} 18 | 19 | updated_alan = rename_keys(alan_moore, {'bookIsbns': 'books'}) 20 | show(alan_moore, updated_alan) 21 | 22 | 23 | book_item = { 24 | 'id': 'book-item-1', 25 | 'rackId': 'rack-17', 26 | 'isLent': True} 27 | 28 | updated_book_item = rename_keys(book_item, {'rackId': 'id', 'id': 'bookItemId'}) 29 | show(book_item, updated_book_item) 30 | -------------------------------------------------------------------------------- /challenges/python/retrieve_information.py: -------------------------------------------------------------------------------- 1 | def get_book_property(library_data, isbn, field_name): 2 | return __get(library_data, ['catalog', 'booksByIsbn', isbn, field_name]) 3 | 4 | 5 | def __get(m, ks): 6 | return m[ks[0]] if len(ks) == 1 else __get(m[ks[0]], ks[1:]) 7 | 8 | 9 | if __name__ == '__main__': 10 | from library_data import data 11 | 12 | title = get_book_property(data, '978-1779501127', 'title') 13 | print(title) 14 | -------------------------------------------------------------------------------- /challenges/python/search_information.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | def book_info(library_data, search_string): 4 | ss = search_string.lower() 5 | books = library_data['catalog']['booksByIsbn'] 6 | 7 | results = [{ 8 | 'title': b['title'], 9 | 'isbn': b['isbn'], 10 | 'authorNames': author_names(library_data, b['authorIds'])} 11 | for b in books.values() 12 | if ss in b['title'].lower()] 13 | 14 | return json.dumps(results) 15 | 16 | 17 | def author_names(library_data, author_ids): 18 | authors = library_data['catalog']['authorsById'] 19 | 20 | return [author['name'] 21 | for (author_id, author) in authors.items() 22 | if author_id in author_ids] 23 | 24 | 25 | if __name__ == '__main__': 26 | from library_data import data 27 | 28 | print(book_info(data, 'watCH')) 29 | -------------------------------------------------------------------------------- /challenges/ruby/block-member.rb: -------------------------------------------------------------------------------- 1 | load 'library-data.rb' 2 | 3 | def block_member(library_data, email) 4 | information_path = ['userManagement', 'members', email, 'isBlocked'] 5 | library_data.set_in(information_path, true) 6 | end 7 | 8 | updated_library_data = block_member($library_data, 'samantha@gmail.com') 9 | information_path = ['userManagement', 'members', 'samantha@gmail.com', 'isBlocked'] 10 | pp [updated_library_data.dig(*information_path), $library_data.dig(*information_path)] 11 | -------------------------------------------------------------------------------- /challenges/ruby/book-property.rb: -------------------------------------------------------------------------------- 1 | load 'library-data.rb' 2 | 3 | def get_book_property(library_data, isbn, field_name) 4 | information_path = ['catalog', 'booksByIsbn', isbn, field_name] 5 | library_data.dig(*information_path) 6 | end 7 | 8 | pp get_book_property($library_data, '978-1779501127', 'title') 9 | -------------------------------------------------------------------------------- /challenges/ruby/diff.rb: -------------------------------------------------------------------------------- 1 | load 'library-data.rb' 2 | 3 | def diff(data1, data2) 4 | return {} if data1 == data2 5 | return data2 if data1 != data2 && data1.class != Hash 6 | keys = data1.keys | data2.keys 7 | keys.reduce({}) do |acc, k| 8 | result = diff(data1[k], data2[k]) 9 | result = (result.class == Hash && result.empty?) ? {} : {k => result} 10 | acc.merge(result) 11 | end 12 | end 13 | 14 | updated_library_data = $library_data.set_in(['userManagement', 'members', 'samantha@gmail.com', 'isBlocked'], true) 15 | 16 | pp diff($library_data, updated_library_data) 17 | pp diff($library_data, $library_data) 18 | -------------------------------------------------------------------------------- /challenges/ruby/rename-keys.rb: -------------------------------------------------------------------------------- 1 | load 'library-data.rb' 2 | 3 | def rename_keys(map, key_map) 4 | new_map = map.transform_keys(key_map) 5 | map 6 | .merge(new_map) 7 | .reject { |k, _| key_map.keys.include?(k) } 8 | end 9 | 10 | alan_moore = { 11 | 'name' => 'Alan Moore', 12 | 'bookIsbns' => ['978-1779501127'] 13 | } 14 | 15 | pp rename_keys(alan_moore, {'bookIsbns' => 'books'}) 16 | 17 | book_item = { 18 | 'id' => 'book-item-1', 19 | 'rackId' => 'rack-17', 20 | 'isLent' => true 21 | } 22 | 23 | pp rename_keys(book_item, {'rackId' => 'id', 'id' => 'bookItemId'}) 24 | -------------------------------------------------------------------------------- /challenges/ruby/search-book.rb: -------------------------------------------------------------------------------- 1 | load 'library-data.rb' 2 | 3 | def author_names(catalog_data, book) 4 | book['authorIds'].map do |author_id| 5 | catalog_data.dig('authorsById', author_id, 'name') 6 | end 7 | end 8 | 9 | def book_info(catalog_data, book) 10 | { 11 | 'title' => book['title'], 12 | 'isbn' => book['isbn'], 13 | 'authorNames' => author_names(catalog_data, book) 14 | } 15 | end 16 | 17 | def search_books_by_title(library_data, query) 18 | catalog_data = library_data['catalog'] 19 | all_books = catalog_data['booksByIsbn'] 20 | matching_books = all_books.select do |_, book| 21 | book['title'].downcase.include?(query.downcase) 22 | end 23 | matching_books.map { |_, book| book_info(catalog_data, book) }.to_json 24 | end 25 | 26 | pp search_books_by_title($library_data, 'watCH') 27 | -------------------------------------------------------------------------------- /challenges/typescript/README.md: -------------------------------------------------------------------------------- 1 | Implementation by @shybyte 2 | 3 | More Data-Oriented TypeScript gems at https://github.com/shybyte/data-oriented-programming-book-experiments 4 | -------------------------------------------------------------------------------- /challenges/typescript/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "data-oriented-programming-book-experiments", 3 | "version": "1.0.0", 4 | "main": "index.js", 5 | "author": "shybyte@users.noreply.github.com", 6 | "license": "MIT", 7 | "scripts": { 8 | "test": "jest", 9 | "tsc:watch": "tsc --watch" 10 | }, 11 | "devDependencies": { 12 | "@types/jest": "^26.0.21", 13 | "@types/lodash": "^4.14.168", 14 | "jest": "^26.6.3", 15 | "ts-jest": "^26.5.4", 16 | "ts-node": "^9.1.1", 17 | "typescript": "^4.2.3" 18 | }, 19 | "dependencies": { 20 | "@types/deep-freeze": "^0.1.2", 21 | "deep-freeze": "^0.0.1", 22 | "fp-ts": "^2.9.5", 23 | "immer": "^9.0.1", 24 | "lodash": "^4.17.21", 25 | "monocle-ts": "^2.3.9" 26 | }, 27 | "jest": { 28 | "moduleFileExtensions": [ 29 | "js", 30 | "json", 31 | "ts", 32 | "tsx" 33 | ], 34 | "rootDir": ".", 35 | "testRegex": "test/*.ts$", 36 | "transform": { 37 | "^.+\\.(t|j)s(x?)$": "ts-jest" 38 | }, 39 | "coverageDirectory": "../coverage", 40 | "testEnvironment": "node" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /challenges/typescript/src/block-member.ts: -------------------------------------------------------------------------------- 1 | import produce from 'immer'; 2 | import {Library} from './data-model'; 3 | import {libraryData} from './test-data'; 4 | 5 | function blockMember(library: Library, email: string) { 6 | return produce(library, (libraryDraft) => { 7 | libraryDraft.userManagement.membersByEmail[email].isBlocked = true; 8 | }); 9 | } 10 | 11 | describe('Challenge 3 - Add a piece of information', () => { 12 | test('blockMember should not modify the original library', () => { 13 | const updatedLibraryData = blockMember(libraryData, 'samantha@gmail.com'); 14 | 15 | expect(updatedLibraryData.userManagement.membersByEmail['samantha@gmail.com'].isBlocked).toEqual(true); 16 | expect(libraryData.userManagement.membersByEmail['samantha@gmail.com'].isBlocked).toEqual(false); 17 | }); 18 | }); -------------------------------------------------------------------------------- /challenges/typescript/src/utils.ts: -------------------------------------------------------------------------------- 1 | export function getKeys(object: T): Array { 2 | return Object.keys(object) as Array; 3 | } 4 | 5 | export function isObject(object: unknown): object is {} | unknown[] { 6 | return typeof object === 'object'; 7 | } -------------------------------------------------------------------------------- /community/README.md: -------------------------------------------------------------------------------- 1 | This is where the Data-Oriented Programming community share links to projects that implement ideas of the book. 2 | 3 | 4 | ## Multimethods 5 | 6 | - Multimethod implementation in TypeScript: https://github.com/astridlyre/fp/blob/master/src/multimethod/multimethod.ts 7 | -------------------------------------------------------------------------------- /src/appendix-a/add-author-request-schema.js: -------------------------------------------------------------------------------- 1 | var addAuthorRequestSchema = { 2 | "type": "object", // <1> 3 | "required": ["firstName", "lastName"], // <2> 4 | "properties": { 5 | "firstName": {"type": "string"}, // <3> 6 | "lastName": {"type": "string"}, // <4> 7 | "books": {"type": "integer"} // <5> 8 | } 9 | }; 10 | -------------------------------------------------------------------------------- /src/appendix-a/add-on-the-fly.js: -------------------------------------------------------------------------------- 1 | var data = createAuthorData("Isaac", "Asimov", 500); 2 | data.fullName = "Isaac Asimov"; 3 | -------------------------------------------------------------------------------- /src/appendix-a/author-data.cs: -------------------------------------------------------------------------------- 1 | var asimov = new Dictionary(); 2 | asimov["name"] = "Isaac Asimov"; 3 | asimov["books"] = 500; 4 | 5 | -------------------------------------------------------------------------------- /src/appendix-a/author-data.java: -------------------------------------------------------------------------------- 1 | var asimov = new HashMap(); 2 | 3 | asimov.put("firstName", "Isaac"); 4 | asimov.put("lastName", "Asimov"); 5 | asimov.put("books", 500); 6 | 7 | -------------------------------------------------------------------------------- /src/appendix-a/author-info-refactored.js: -------------------------------------------------------------------------------- 1 | function displayFullName(authorData) { 2 | console.log("Author full name is: ", NameCalculation.fullName(authorData)); 3 | } 4 | 5 | function displayProlificity(authorData) { 6 | if(authorData.books == null) { 7 | console.log("Author has not written any book"); 8 | } else { 9 | if (AuthorRating.isProlific(authorData)) { 10 | console.log("Author is prolific"); 11 | } else { 12 | console.log("Author is not prolific"); 13 | } 14 | } 15 | } 16 | 17 | function displayAuthorInfo(authorData) { 18 | if(!ajv.validate(authorSchema, authorData)) { 19 | throw "displayAuthorInfo called with invalid data"; 20 | }; 21 | displayFullName(authorData); 22 | displayProlificity(authorData); 23 | } 24 | -------------------------------------------------------------------------------- /src/appendix-a/author-info.js: -------------------------------------------------------------------------------- 1 | class NameCalculation { 2 | static fullName(data) { 3 | return data.firstName + " " + data.lastName; 4 | } 5 | } 6 | 7 | class AuthorRating { 8 | static isProlific (data) { 9 | return data.books > 100; 10 | } 11 | } 12 | 13 | var authorSchema = { 14 | "type": "object", 15 | "required": ["firstName", "lastName"], 16 | "properties": { 17 | "firstName": {"type": "string"}, 18 | "lastName": {"type": "string"}, 19 | "books": {"type": "integer"} 20 | } 21 | }; 22 | 23 | function displayAuthorInfo(authorData) { 24 | if(!ajv.validate(authorSchema, authorData)) { 25 | throw "displayAuthorInfo called with invalid data"; 26 | }; 27 | console.log("Author full name is: ", NameCalculation.fullName(authorData)); 28 | if(authorData.books == null) { 29 | console.log("Author has not written any book"); 30 | } else { 31 | if (AuthorRating.isProlific(authorData)) { 32 | console.log("Author is prolific"); 33 | } else { 34 | console.log("Author is not prolific"); 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/appendix-a/author-invalid-books.js: -------------------------------------------------------------------------------- 1 | var authorDataInvalidBooks = { 2 | "firstName": "Albert", 3 | "lastName": "Einstein", 4 | "books": "Five" 5 | }; 6 | 7 | validate(authorSchema, authorDataInvalidBooks); // <1> 8 | // → false 9 | -------------------------------------------------------------------------------- /src/appendix-a/author-no-books.js: -------------------------------------------------------------------------------- 1 | var authorDataNoBooks = { 2 | "firstName": "Yehonathan", 3 | "lastName": "Sharvit" 4 | }; 5 | 6 | ajv.validate(authorSchema, authorDataNoBooks); // <1> 7 | // → true 8 | -------------------------------------------------------------------------------- /src/appendix-a/author-schema-complex.js: -------------------------------------------------------------------------------- 1 | var authorComplexSchema = { 2 | "type": "object", 3 | "required": ["firstName", "lastName"], 4 | "properties": { 5 | "firstName": { 6 | "type": "string", 7 | "maxLength": 100 8 | }, 9 | "lastName": { 10 | "type": "string", 11 | "maxLength": 100 12 | }, 13 | "books": { 14 | "type": "integer", 15 | "minimum": 0, 16 | "maximum": 10000 17 | } 18 | } 19 | }; 20 | -------------------------------------------------------------------------------- /src/appendix-a/author-schema-visualize.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "object", 3 | "required": ["firstName", "lastName"], 4 | "properties": { 5 | "firstName": {"type": "string"}, 6 | "lastName": {"type": "string"}, 7 | "bookList": { 8 | "type": "array", 9 | "items": { 10 | "type": "object", 11 | "properties": { 12 | "title": {"type": "string"}, 13 | "publicationYear": {"type": "integer"} 14 | } 15 | } 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/appendix-a/author-schema.js: -------------------------------------------------------------------------------- 1 | var authorSchema = { 2 | "type": "object", 3 | "required": ["firstName", "lastName"], // <1> 4 | "properties": { 5 | "firstName": {"type": "string"}, 6 | "lastName": {"type": "string"}, 7 | "books": {"type": "number"} // <2> 8 | } 9 | }; 10 | -------------------------------------------------------------------------------- /src/appendix-a/breaking-1-fp.js: -------------------------------------------------------------------------------- 1 | function createAuthorObject(firstName, lastName, books) { 2 | return { 3 | fullName: function() { 4 | return firstName + " " + lastName; 5 | }, 6 | isProlific: function () { 7 | return books > 100; 8 | } 9 | }; 10 | } 11 | 12 | var obj = createAuthorObject("Isaac", "Asimov", 500); 13 | obj.fullName(); 14 | // → "Isaac Asimov" 15 | -------------------------------------------------------------------------------- /src/appendix-a/breaking-1-oo.js: -------------------------------------------------------------------------------- 1 | class Author { 2 | constructor(firstName, lastName, books) { 3 | this.firstName = firstName; 4 | this.lastName = lastName; 5 | this.books = books; 6 | } 7 | fullName() { 8 | return this.firstName + " " + this.lastName; 9 | } 10 | isProlific() { 11 | return this.books > 100; 12 | } 13 | } 14 | 15 | var obj = new Author("Isaac", "Asimov", 500); // <1> 16 | obj.fullName(); 17 | // → "Isaac Asimov" 18 | -------------------------------------------------------------------------------- /src/appendix-a/breaking-2-oo.js: -------------------------------------------------------------------------------- 1 | class AuthorData { 2 | constructor(firstName, lastName, books) { 3 | this.firstName = firstName; 4 | this.lastName = lastName; 5 | this.books = books; 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/appendix-a/check-data-validity.js: -------------------------------------------------------------------------------- 1 | var validAuthorData = { 2 | firstName: "Isaac", 3 | lastName: "Asimov", 4 | books: 500 5 | }; 6 | 7 | ajv.validate(addAuthorRequestSchema, validAuthorData); // <1> 8 | // → true 9 | 10 | var invalidAuthorData = { 11 | firstName: "Isaac", 12 | lastNam: "Asimov", 13 | books: "five hundred" 14 | }; 15 | 16 | ajv.validate(addAuthorRequestSchema, invalidAuthorData); // <2> 17 | // → false 18 | -------------------------------------------------------------------------------- /src/appendix-a/data-validation-errors.js: -------------------------------------------------------------------------------- 1 | var invalidAuthorData = { 2 | firstName: "Isaac", 3 | lastNam: "Asimov", 4 | books: "five hundred" 5 | }; 6 | 7 | var ajv = new Ajv({allErrors: true}); // <1> 8 | ajv.validate(addAuthorRequestSchema, invalidAuthorData); 9 | ajv.errorsText(ajv.errors); // <2> 10 | // → "data should have required property 'lastName', data.books should be number" 11 | -------------------------------------------------------------------------------- /src/appendix-a/duplicating-oo.js: -------------------------------------------------------------------------------- 1 | function createAuthorObject(firstName, lastName, books) { 2 | var data = {firstName: firstName, lastName: lastName, books: books}; 3 | 4 | return { 5 | fullName: function fullName() { 6 | return data.firstName + " " + data.lastName; 7 | } 8 | }; 9 | } 10 | 11 | function createUserObject(firstName, lastName, email) { 12 | var data = {firstName: firstName, lastName: lastName, email: email}; 13 | 14 | return { 15 | fullName: function fullName() { 16 | return data.firstName + " " + data.lastName; 17 | } 18 | }; 19 | } 20 | 21 | var obj = createUserObject("John", "Doe", "john@doe.com"); 22 | obj.fullName(); 23 | // → "John Doe" 24 | 25 | -------------------------------------------------------------------------------- /src/appendix-a/following-1-fp.js: -------------------------------------------------------------------------------- 1 | function createAuthorData(firstName, lastName, books) { 2 | return { 3 | firstName: firstName, 4 | lastName: lastName, 5 | books: books 6 | }; 7 | } 8 | 9 | function fullName(data) { 10 | return data.firstName + " " + data.lastName; 11 | } 12 | 13 | function isProlific (data) { 14 | return data.books > 100; 15 | } 16 | 17 | var data = createAuthorData("Isaac", "Asimov", 500); 18 | fullName(data); 19 | // → "Isaac Asimov" 20 | -------------------------------------------------------------------------------- /src/appendix-a/following-1-oo.js: -------------------------------------------------------------------------------- 1 | class AuthorData { 2 | constructor(firstName, lastName, books) { 3 | this.firstName = firstName; 4 | this.lastName = lastName; 5 | this.books = books; 6 | } 7 | } 8 | 9 | class NameCalculation { 10 | static fullName(data) { 11 | return data.firstName + " " + data.lastName; 12 | } 13 | } 14 | 15 | class AuthorRating { 16 | static isProlific (data) { 17 | return data.books > 100; 18 | } 19 | } 20 | 21 | var data = new AuthorData("Isaac", "Asimov", 500); 22 | NameCalculation.fullName(data); 23 | // → "Isaac Asimov" 24 | 25 | -------------------------------------------------------------------------------- /src/appendix-a/following-2-literal.js: -------------------------------------------------------------------------------- 1 | function createAuthorData(firstName, lastName, books) { 2 | return { 3 | firstName: firstName, 4 | lastName: lastName, 5 | books: books 6 | }; 7 | } 8 | -------------------------------------------------------------------------------- /src/appendix-a/following-2-oo.js: -------------------------------------------------------------------------------- 1 | function createAuthorData(firstName, lastName, books) { 2 | var data = new Map; 3 | data.firstName = firstName; 4 | data.lastName = lastName; 5 | data.books = books; 6 | return data; 7 | } 8 | -------------------------------------------------------------------------------- /src/appendix-a/full-name-oo.js: -------------------------------------------------------------------------------- 1 | class Author { 2 | constructor(firstName, lastName, books) { 3 | this.firstName = firstName; 4 | this.lastName = lastName; 5 | this.books = books; 6 | } 7 | fullName() { 8 | return this.firstName + " " + this.lastName; 9 | } 10 | isProlific() { 11 | return this.books > 100; 12 | } 13 | } 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/appendix-a/is-prolific.cs: -------------------------------------------------------------------------------- 1 | class AuthorRating { 2 | public static bool isProlific (Dictionary data) { 3 | return data["books"] > 100; 4 | } 5 | } 6 | 7 | -------------------------------------------------------------------------------- /src/appendix-a/is-prolific.java: -------------------------------------------------------------------------------- 1 | class AuthorRating { 2 | static boolean isProlific (Map data) { 3 | return (int)data.get("books") > 100; 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /src/appendix-a/manipulate-generic.js: -------------------------------------------------------------------------------- 1 | var data = createAuthorData("Isaac", "Asimov", 500); 2 | var dataWithoutBooks = _.pick(data, ["firstName", "lastName"]); 3 | JSON.stringify(dataWithoutBooks); 4 | // → "{\"firstName\":\"Isaac\",\"lastName\":\"Asimov\"}" 5 | -------------------------------------------------------------------------------- /src/appendix-a/mutating-in-place.js: -------------------------------------------------------------------------------- 1 | var myData = {num: 42}; 2 | var yourData = myData; 3 | 4 | yourData.num = yourData.num + 1; 5 | console.log(myData.num); 6 | // → 43 7 | -------------------------------------------------------------------------------- /src/appendix-a/mutating-via-cloning.js: -------------------------------------------------------------------------------- 1 | function changeValue(obj, k, v) { 2 | var res = Object.assign({}, obj); 3 | res[k] = v; 4 | return res; 5 | } 6 | 7 | var myData = {num: 42}; 8 | var yourData = changeValue(myData, "num", myData.num + 1); 9 | console.log(myData.num); 10 | // → 43 11 | -------------------------------------------------------------------------------- /src/appendix-a/mutating-via-immutable.js: -------------------------------------------------------------------------------- 1 | var myData = Immutable.Map({num: 42}) 2 | var yourData = myData.set("num", 43); 3 | console.log(yourData.get("num")); 4 | // → 43 5 | console.log(myData.get("num")); 6 | // → 42 7 | -------------------------------------------------------------------------------- /src/appendix-a/not-conforming.js: -------------------------------------------------------------------------------- 1 | fullName({fistName: "Issac", lastName: "Asimov"}); 2 | // → "undefined Asimov" 3 | -------------------------------------------------------------------------------- /src/appendix-a/receive-data.js: -------------------------------------------------------------------------------- 1 | function fullName(data) { 2 | return data.firstName + " " + data.lastName; 3 | } 4 | -------------------------------------------------------------------------------- /src/appendix-a/serialize-free.js: -------------------------------------------------------------------------------- 1 | var data = createAuthorData("Isaac", "Asimov", 500); 2 | JSON.stringify(data); 3 | // → "{\"firstName\":\"Isaac\",\"lastName\":\"Asimov\",\"books\":500}" 4 | -------------------------------------------------------------------------------- /src/appendix-a/testing-full-oo.js: -------------------------------------------------------------------------------- 1 | var author = createAuthorObject("Isaac", "Asimov", 500); 2 | author.fullName() === "Isaac Asimov" 3 | // → true 4 | -------------------------------------------------------------------------------- /src/appendix-a/testing-isolated-fp.js: -------------------------------------------------------------------------------- 1 | var author = { 2 | firstName: "Isaac", 3 | lastName: "Asimov" 4 | }; 5 | fullName(author) === "Isaac Asimov" 6 | // → true 7 | -------------------------------------------------------------------------------- /src/appendix-a/testing-isolated-oo.js: -------------------------------------------------------------------------------- 1 | var data = new AuthorData("Isaac", "Asimov"); 2 | 3 | NameCalculation.fullName(data) === "Isaac Asimov" 4 | // → true 5 | -------------------------------------------------------------------------------- /src/appendix-a/unpredictable-async.js: -------------------------------------------------------------------------------- 1 | var myData = {num: 42}; 2 | setTimeout(function (data){ 3 | console.log(data.num); 4 | }, 1000, myData); 5 | myData.num = 0; 6 | -------------------------------------------------------------------------------- /src/appendix-a/using-same-fp.js: -------------------------------------------------------------------------------- 1 | function createAuthorData(firstName, lastName, books) { 2 | return {firstName: firstName, lastName: lastName, books: books}; 3 | } 4 | 5 | function fullName(data) { 6 | return data.firstName + " " + data.lastName; 7 | } 8 | 9 | function createUserData(firstName, lastName, email) { 10 | return {firstName: firstName, lastName: lastName, email: email}; 11 | } 12 | 13 | var authorData = createAuthorData("Isaac", "Asimov", 500); 14 | fullName(authorData); 15 | 16 | var userData = createUserData("John", "Doe", "john@doe.com"); 17 | fullName(userData); 18 | // → "John Doe" 19 | -------------------------------------------------------------------------------- /src/appendix-a/using-same-oo.js: -------------------------------------------------------------------------------- 1 | class AuthorData { 2 | constructor(firstName, lastName, books) { 3 | this.firstName = firstName; 4 | this.lastName = lastName; 5 | this.books = books; 6 | } 7 | } 8 | 9 | class NameCalculation { 10 | static fullName(data) { 11 | return data.firstName + " " + data.lastName; 12 | } 13 | } 14 | 15 | class UserData { 16 | constructor(firstName, lastName, email) { 17 | this.firstName = firstName; 18 | this.lastName = lastName; 19 | this.email = email; 20 | } 21 | } 22 | 23 | var userData = new UserData("John", "Doe", "john@doe.com"); 24 | NameCalculation.fullName(userData); 25 | 26 | var authorData = new AuthorData("Isaac", "Asimov", 500); 27 | NameCalculation.fullName(authorData); 28 | // → "John Doe" 29 | -------------------------------------------------------------------------------- /src/appendix-b/book-class.java: -------------------------------------------------------------------------------- 1 | public class BookData { 2 | public final String isbn; 3 | public final String title; 4 | public final Integer publicationYear; 5 | public BookData ( 6 | String isbn, 7 | String title, 8 | Integer publicationYear) { 9 | this.isbn = isbn; 10 | this.title = title; 11 | this.publicationYear = publicationYear; 12 | } 13 | 14 | public boolean equals(Object o) { 15 | // Omitted for sake of simplicity 16 | } 17 | 18 | public int hashCode() { 19 | // Omitted for sake of simplicity 20 | } 21 | 22 | public String toString() { 23 | // Omitted for sake of simplicity 24 | } 25 | } 26 | 27 | 28 | -------------------------------------------------------------------------------- /src/appendix-b/book-nested-class.java: -------------------------------------------------------------------------------- 1 | public class BookAttributes { 2 | public Integer numberOfPages; 3 | public String language; 4 | public BookAttributes(Integer numberOfPages, String language) { 5 | this.numberOfPages = numberOfPages; 6 | this.language = language; 7 | } 8 | } 9 | 10 | public class BookWithAttributes { 11 | public String isbn; 12 | public String title; 13 | public Integer publicationYear; 14 | public BookAttributes attributes; 15 | public Book ( 16 | String isbn, 17 | String title, 18 | Integer publicationYear, 19 | Integer numberOfPages, 20 | String language) { 21 | this.isbn = isbn; 22 | this.title = title; 23 | this.publicationYear = publicationYear; 24 | this.attributes = new BookAttributes(numberOfPages, language); 25 | } 26 | } 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /src/appendix-b/book-record.java: -------------------------------------------------------------------------------- 1 | public record BookData (String isbn, 2 | String title, 3 | Integer publicationYear 4 | ) {} 5 | -------------------------------------------------------------------------------- /src/appendix-b/dynamic-access-class-title.java: -------------------------------------------------------------------------------- 1 | ((String)DynamicAccess.get(watchmenRecord, "title")).toUpperCase(); 2 | // → "WATCHMEN" 3 | -------------------------------------------------------------------------------- /src/appendix-b/dynamic-access-class.java: -------------------------------------------------------------------------------- 1 | class DynamicAccess { 2 | static Object get(Object o, String k) { 3 | if(o instanceof Map) { 4 | return ((Map)o).get(k); 5 | } 6 | try { 7 | return (o.getClass().getDeclaredField(k).get(o)); 8 | } catch (IllegalAccessException | NoSuchFieldException e) { 9 | return null; 10 | } 11 | } 12 | 13 | static String getAsString(Object o, String k) { 14 | return (String)get(o, k); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/appendix-b/dynamic-access-map.java: -------------------------------------------------------------------------------- 1 | class DynamicAccess { 2 | static Object get(Map m, String k) { 3 | return (m).get(k); 4 | } 5 | } 6 | 7 | -------------------------------------------------------------------------------- /src/appendix-b/dynamic-access-mapping.java: -------------------------------------------------------------------------------- 1 | var books = List.of(watchmenMap, sevenHabitsMap); 2 | var fieldName = "title"; 3 | 4 | books.stream() 5 | .map(x -> DynamicAccess.get(x, fieldName)) 6 | .map(x -> ((String)x).toUpperCase()) 7 | .collect(Collectors.toList()) 8 | // → ["WATCHMEN", "7 HABITS OF HIGHLY EFFECTIVE PEOPLE"] 9 | -------------------------------------------------------------------------------- /src/appendix-b/dynamic-access-nested-class-language.java: -------------------------------------------------------------------------------- 1 | var informationPath = List.of("attributes", 2 | "language"); 3 | DynamicClassAccess.getAsString(sevenHabitsNestedRecord, informationPath) 4 | .toUpperCase(); 5 | // → "EN" 6 | -------------------------------------------------------------------------------- /src/appendix-b/dynamic-access-nested-class.java: -------------------------------------------------------------------------------- 1 | class DynamicAccess { 2 | static Object get(Object o, String k) { 3 | if(o instanceof Map) { 4 | return ((Map)o).get(k); 5 | } 6 | try { 7 | return (o.getClass().getDeclaredField(k).get(o)); 8 | } catch (IllegalAccessException | NoSuchFieldException e) { 9 | return null; 10 | } 11 | } 12 | 13 | static Object get(Object o, List p) { 14 | Object v = o; 15 | for (String k : p) { 16 | v = get(v, k); 17 | } 18 | return v; 19 | } 20 | 21 | static String getAsString(Object o, String k) { 22 | return (String)get(o, k); 23 | } 24 | 25 | static String getAsString(Object o, List p) { 26 | return (String)get(o, p); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/appendix-b/dynamic-access-nested-map.java: -------------------------------------------------------------------------------- 1 | class DynamicAccess { 2 | static Object get(Map m, String k) { 3 | return (m).get(k); 4 | } 5 | 6 | static Object get(Map m, List path) { 7 | Object v = m; 8 | for (String k : path) { 9 | v = get((Map)v, k); 10 | if (v == null) { 11 | return null; 12 | } 13 | } 14 | return v; 15 | } 16 | } 17 | 18 | 19 | -------------------------------------------------------------------------------- /src/appendix-b/get-casting-nested.java: -------------------------------------------------------------------------------- 1 | ((String)DynamicAccess.get(searchResultsMap, 2 | List.of("978-1779501127", "title"))).toUpperCase(); 3 | // → "WATCHMEN" 4 | 5 | -------------------------------------------------------------------------------- /src/appendix-b/get-casting.java: -------------------------------------------------------------------------------- 1 | ((String)DynamicAccess.get(watchmenMap, "title")).toUpperCase(); 2 | // → "WATCHMEN" 3 | -------------------------------------------------------------------------------- /src/appendix-b/get-value-getter-nested.java: -------------------------------------------------------------------------------- 1 | var informationPath = List.of("978-1779501127", "title"); 2 | 3 | DynamicAccess.getAsString(searchResultsMap, informationPath) 4 | .toUpperCase(); 5 | // → "WATCHMEN" 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/appendix-b/gson-example-nested.java: -------------------------------------------------------------------------------- 1 | BookData sevenHabitsNestedRecord = new BookWithAttributes( 2 | "978-1982137274", 3 | "7 Habits of Highly Effective People", 4 | 2020, 5 | 432, 6 | "en" 7 | ); 8 | 9 | System.out.println(gson.toJson(sevenHabitsNestedRecord)); 10 | // → {"isbn":"978-1982137274","title":"7 Habits of Highly Effective People", …} 11 | 12 | -------------------------------------------------------------------------------- /src/appendix-b/gson-example-search-results.java: -------------------------------------------------------------------------------- 1 | Map searchResultsRecords = Map.of( 2 | "978-1779501127", new BookData( 3 | "978-1779501127", 4 | "Watchmen", 5 | 1987 6 | ), 7 | "978-1982137274", new BookData( 8 | "978-1982137274", 9 | "7 Habits of Highly Effective People", 10 | 2020 11 | ) 12 | ); 13 | 14 | System.out.println(gson.toJson(searchResultsRecords)); 15 | // → {"978-1779501127":{"isbn":"978-1779501127","title":"Watchmen", …}} 16 | 17 | -------------------------------------------------------------------------------- /src/appendix-b/gson-example.java: -------------------------------------------------------------------------------- 1 | import com.google.gson.*; 2 | var gson = new Gson(); 3 | 4 | BookData sevenHabitsRecord = new BookData( 5 | "978-1982137274", 6 | "7 Habits of Highly Effective People", 7 | 2020 8 | ); 9 | 10 | System.out.println(gson.toJson(sevenHabitsRecord)); 11 | // → {"title":"7 Habits of Highly Effective People", …} 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/appendix-b/search-results-map.java: -------------------------------------------------------------------------------- 1 | Map searchResultsMap = Map.of( 2 | "978-1779501127", Map.of( 3 | "isbn", "978-1779501127", 4 | "title", "Watchmen", 5 | "publicationYear", 1987 6 | ), 7 | "978-1982137274", Map.of( 8 | "isbn", "978-1982137274", 9 | "title", "7 Habits of Highly Effective People", 10 | "publicationYear", 2020 11 | ) 12 | ); 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/appendix-b/search-results-records.java: -------------------------------------------------------------------------------- 1 | Map searchResultsRecords = Map.of( 2 | "978-1779501127", new BookData( 3 | "978-1779501127", 4 | "Watchmen", 5 | 1987 6 | ), 7 | "978-1982137274", new BookData( 8 | "978-1982137274", 9 | "7 Habits of Highly Effective People", 10 | 2020 11 | ) 12 | ); 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/appendix-b/seven-nested-record.java: -------------------------------------------------------------------------------- 1 | BookData sevenHabitsNestedRecord = new BookWithAttributes( 2 | "978-1982137274", 3 | "7 Habits of Highly Effective People", 4 | 2020, 5 | 432, 6 | "en" 7 | ); 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/appendix-b/typed-getter-impl.java: -------------------------------------------------------------------------------- 1 | class Getter { 2 | private String key; 3 | 4 | public Getter (String k) { 5 | this.key = k; 6 | } 7 | 8 | public T get (Map m) { 9 | return (T)(DynamicAccess.get(m, key)); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/appendix-b/typed-getter-mapping-objects.java: -------------------------------------------------------------------------------- 1 | var books = List.of(watchmenRecord, sevenHabitsRecord); 2 | 3 | books.stream() 4 | .map(x -> TITLE.get(x)) 5 | .map(x -> x.toUpperCase()) 6 | .collect(Collectors.toList()) 7 | // → ["WATCHMEN", "7 HABITS OF HIGHLY EFFECTIVE PEOPLE"] 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/appendix-b/typed-getter-mapping.java: -------------------------------------------------------------------------------- 1 | var books = List.of(watchmenMap, sevenHabitsMap); 2 | 3 | books.stream() 4 | .map(x -> TITLE.get(x)) 5 | .map(x -> x.toUpperCase()) 6 | .collect(Collectors.toList()) 7 | // → ["WATCHMEN", "7 HABITS OF HIGHLY EFFECTIVE PEOPLE"] 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/appendix-b/typed-getter-nested-impl.java: -------------------------------------------------------------------------------- 1 | class Getter { 2 | private List path; 3 | private String key; 4 | private boolean nested; 5 | 6 | public Getter (List path) { 7 | this.path = path; 8 | nested = true; 9 | } 10 | 11 | public Getter (String k) { 12 | this.key = k; 13 | nested = false; 14 | } 15 | 16 | public T get (Map m) { 17 | if(nested) { 18 | return (T)(DynamicAccess.get(m, path)); 19 | } 20 | return (T)(DynamicAccess.get(m, key)); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/appendix-b/typed-getter-nested-title.java: -------------------------------------------------------------------------------- 1 | var informationPath = List.of("978-1779501127", 2 | "title"); 3 | 4 | Getter NESTED_TITLE = new Getter(informationPath); 5 | NESTED_TITLE.get(searchResultsMap).toUpperCase(); 6 | // → "WATCHMEN" 7 | -------------------------------------------------------------------------------- /src/appendix-b/typed-getter-title.java: -------------------------------------------------------------------------------- 1 | Getter TITLE = new Getter("title"); 2 | TITLE.get(watchmenMap).toUpperCase(); 3 | // → "WATCHMEN" 4 | -------------------------------------------------------------------------------- /src/appendix-b/value-getter-class-mapping.java: -------------------------------------------------------------------------------- 1 | var books = List.of(watchmenRecord, sevenHabitsRecord); 2 | 3 | books.stream() 4 | .map(x -> DynamicAccess.getAsString(x, "title")) 5 | .map(x -> x.toUpperCase()) 6 | .collect(Collectors.toList()) 7 | // → ["WATCHMEN", "7 HABITS OF HIGHLY EFFECTIVE PEOPLE"] 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/appendix-b/value-getter-class-title.java: -------------------------------------------------------------------------------- 1 | DynamicAccess.getAsString(watchmenRecord, "title").toUpperCase(); 2 | // → "WATCHMEN" 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/appendix-b/value-getter-map-nested.java: -------------------------------------------------------------------------------- 1 | class DynamicAccess { 2 | static Object get(Map m, String k) { 3 | return (m).get(k); 4 | } 5 | 6 | static Object get(Map m, List p) { 7 | Object v = m; 8 | for (String k : p) { 9 | v = get((Map)v, k); 10 | if (v == null) { 11 | return null; 12 | } 13 | } 14 | return v; 15 | } 16 | 17 | static String getAsString(Map m, String k) { 18 | return (String)get(m, k); 19 | } 20 | 21 | static String getAsString(Map m, List p) { 22 | return (String)get(m, p); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/appendix-b/value-getter-map.java: -------------------------------------------------------------------------------- 1 | class DynamicAccess { 2 | static Object get(Map m, String k) { 3 | return (m).get(k); 4 | } 5 | 6 | static String getAsString(Map m, String k) { 7 | return (String)get(m, k); 8 | } 9 | } 10 | 11 | -------------------------------------------------------------------------------- /src/appendix-b/value-getter-mapping.java: -------------------------------------------------------------------------------- 1 | var books = List.of(watchmenMap, sevenHabitsMap); 2 | 3 | books.stream() 4 | .map(x -> DynamicAccess.getAsString(x, "title")) 5 | .map(x -> x.toUpperCase()) 6 | .collect(Collectors.toList()) 7 | // → ["WATCHMEN", "7 HABITS OF HIGHLY EFFECTIVE PEOPLE"] 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/appendix-b/value-getter-nested-class-title.java: -------------------------------------------------------------------------------- 1 | var informationPath = List.of("978-1779501127", "title"); 2 | DynamicClassAccess 3 | .getAsString(searchResultsRecords, informationPath) 4 | .toUpperCase(); 5 | // → "WATCHMEN" 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/appendix-b/value-getter-title-map.java: -------------------------------------------------------------------------------- 1 | DynamicAccess.getAsString(watchmenMap, "title").toUpperCase(); 2 | // → "WATCHMEN" 3 | 4 | -------------------------------------------------------------------------------- /src/appendix-b/watchmen-map.java: -------------------------------------------------------------------------------- 1 | Map watchmenMap = Map.of( 2 | "isbn", "978-1779501127", 3 | "title", "Watchmen", 4 | "publicationYear", 1987 5 | ); 6 | 7 | Map sevenHabitsMap = Map.of( 8 | "isbn", "978-1982137274", 9 | "title", "7 Habits of Highly Effective People", 10 | "publicationYear", 2020 11 | ); 12 | 13 | -------------------------------------------------------------------------------- /src/appendix-b/watchmen-record.java: -------------------------------------------------------------------------------- 1 | BookData watchmenRecord = new BookData( 2 | "978-1779501127", 3 | "Watchmen", 4 | 1987 5 | ); 6 | 7 | BookData sevenHabitsRecord = new BookData( 8 | "978-1982137274", 9 | "7 Habits of Highly Effective People", 10 | 2020 11 | ); 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/appendix-b/watchmen-title-reflection.java: -------------------------------------------------------------------------------- 1 | watchmenRecord 2 | .getClass() 3 | .getDeclaredField("title") 4 | .get(watchmenRecord) 5 | // → "watchmen" 6 | 7 | -------------------------------------------------------------------------------- /src/appendix-d/lodash-config.js: -------------------------------------------------------------------------------- 1 | _ = fp.convert({ 2 | "cap": false, 3 | "curry": false, 4 | "fixed": false, 5 | "immutable": true, 6 | "rearg": false 7 | }); 8 | -------------------------------------------------------------------------------- /src/chapter01/apparently-simple.js: -------------------------------------------------------------------------------- 1 | class Member { 2 | isBlocked; 3 | 4 | displayBlockedStatusTwice() { 5 | console.log(this.isBlocked); 6 | console.log(this.isBlocked); 7 | } 8 | } 9 | 10 | member.displayBlockedStatusTwice(); 11 | -------------------------------------------------------------------------------- /src/chapter01/really-simple.js: -------------------------------------------------------------------------------- 1 | class Member { 2 | isBlocked; 3 | 4 | displayBlockedStatusTwice() { 5 | var isBlocked = this.isBlocked; 6 | console.log(isBlocked); 7 | console.log(isBlocked); 8 | } 9 | } 10 | 11 | member.displayBlockedStatusTwice(); 12 | -------------------------------------------------------------------------------- /src/chapter01/search-input.json: -------------------------------------------------------------------------------- 1 | { 2 | "searchCriteria": "author", 3 | "query": "albert" 4 | } 5 | -------------------------------------------------------------------------------- /src/chapter01/search-output.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "title": "The world as I see it", 4 | "authors": [ 5 | { 6 | "fullName": "Albert Einstein" 7 | } 8 | ] 9 | }, 10 | { 11 | "title": "The Stranger", 12 | "authors": [ 13 | { 14 | "fullName": "Albert Camus" 15 | } 16 | ] 17 | } 18 | ] 19 | -------------------------------------------------------------------------------- /src/chapter02/add-book-item-signature.js: -------------------------------------------------------------------------------- 1 | class Library { 2 | static addBookItem(libraryData, userId, bookItemInfo) { 3 | } 4 | } 5 | -------------------------------------------------------------------------------- /src/chapter02/add-book-item-vip.js: -------------------------------------------------------------------------------- 1 | class Library { 2 | static addBookItem(libraryData, userId, bookItemInfo) { 3 | if(UserManagement.isLibrarian(libraryData.userManagement, userId) || 4 | UserManagement.isVIPMember(libraryData.userManagement, userId)) { 5 | return Catalog.addBookItem(libraryData.catalog, bookItemInfo); 6 | } else { 7 | throw "Not allowed to add a book item"; // <1> 8 | } 9 | } 10 | } 11 | 12 | class UserManagement { 13 | static isLibrarian(userManagementData, userId) { 14 | // will be implemented later <2> 15 | } 16 | static isVIPMember(userManagementData, userId) { 17 | // will be implemented later <2> 18 | } 19 | } 20 | 21 | class Catalog { 22 | static addBookItem(catalogData, memberId) { 23 | // will be implemented later <3> 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/chapter02/get-book-lending-oo-python.py: -------------------------------------------------------------------------------- 1 | class Library: 2 | catalog = {} 3 | userManagement = {} 4 | 5 | def getBookLendings(self, userId, memberId): 6 | # accesses library state via self.catalog and self.userManagement 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/chapter02/get-book-lendings-do.js: -------------------------------------------------------------------------------- 1 | class Library { 2 | static getBookLendings(libraryData, userId, memberId) { 3 | } 4 | } 5 | -------------------------------------------------------------------------------- /src/chapter02/get-book-lendings-library.js: -------------------------------------------------------------------------------- 1 | class Library { 2 | static getBookLendings(libraryData, userId, memberId) { 3 | if(UserManagement.isLibrarian(libraryData.userManagement, userId)) { 4 | return Catalog.getBookLendings(libraryData.catalog, memberId); 5 | } else { 6 | throw "Not allowed to get book lendings"; // <1> 7 | } 8 | } 9 | } 10 | 11 | class UserManagement { 12 | static isLibrarian(userManagementData, userId) { 13 | // will be implemented later <2> 14 | } 15 | } 16 | 17 | class Catalog { 18 | static getBookLendings(catalogData, memberId) { 19 | // will be implemented later <3> 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/chapter02/get-book-lendings-oo.js: -------------------------------------------------------------------------------- 1 | class Library { 2 | catalog 3 | userManagement 4 | 5 | getBookLendings(userId, memberId) { 6 | // accesses library state via this.catalog and this.userManagement 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/chapter02/get-book-lendings-super.js: -------------------------------------------------------------------------------- 1 | class Library { 2 | static getBookLendings(libraryData, userId, memberId) { 3 | if(Usermanagement.isLibrarian(libraryData.userManagement, userId) || 4 | Usermanagement.isSuperMember(libraryData.userManagement, userId)) { 5 | return Catalog.getBookLendings(libraryData.catalog, memberId); 6 | } else { 7 | throw "Not allowed to get book lendings"; // <1> 8 | } 9 | } 10 | } 11 | 12 | class UserManagement { 13 | static isLibrarian(userManagementData, userId) { 14 | // will be implemented later <2> 15 | } 16 | static isSuperMember(userManagementData, userId) { 17 | // will be implemented later <2> 18 | } 19 | } 20 | 21 | class Catalog { 22 | static getBookLendings(catalogData, memberId) { 23 | // will be implemented later <3> 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/chapter03/author-names.js: -------------------------------------------------------------------------------- 1 | function authorNames(catalogData, book) { 2 | var authorIds = _.get(book, "authorIds"); 3 | var names = _.map(authorIds, function(authorId) { 4 | return _.get(catalogData, ["authorsById", authorId, "name"]); 5 | }); 6 | return names; 7 | } 8 | -------------------------------------------------------------------------------- /src/chapter03/book-info.js: -------------------------------------------------------------------------------- 1 | function bookInfo(catalogData, book) { 2 | var bookInfo = { 3 | "title": _.get(book, "title"), 4 | "isbn": _.get(book, "isbn"), 5 | "authorNames": authorNames(catalogData, book) 6 | }; 7 | return bookInfo; //<1> 8 | } 9 | -------------------------------------------------------------------------------- /src/chapter03/catalog-data.js: -------------------------------------------------------------------------------- 1 | var catalogData = { 2 | "booksByIsbn": { 3 | "978-1779501127": { 4 | "isbn": "978-1779501127", 5 | "title": "Watchmen", 6 | "publicationYear": 1987, 7 | "authorIds": ["alan-moore", "dave-gibbons"], 8 | "bookItems": [ 9 | { 10 | "id": "book-item-1", 11 | "libId": "nyc-central-lib", 12 | "isLent": true 13 | }, 14 | { 15 | "id": "book-item-2", 16 | "libId": "nyc-central-lib", 17 | "isLent": false 18 | } 19 | ] 20 | } 21 | }, 22 | "authorsById": { 23 | "alan-moore": { 24 | "name": "Alan Moore", 25 | "bookIsbns": ["978-1779501127"] 26 | }, 27 | "dave-gibbons": { 28 | "name": "Dave Gibbons", 29 | "bookIsbns": ["978-1779501127"] 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/chapter03/custom-map-test.js: -------------------------------------------------------------------------------- 1 | map(["alan-moore", "dave-gibbons"], 2 | function(authorId) { 3 | return _.get(catalogData, ["authorsById", authorId, "name"]); 4 | }); 5 | // → [ "Alan Moore", "Dave Gibbons"] 6 | 7 | -------------------------------------------------------------------------------- /src/chapter03/filter-impl-test.js: -------------------------------------------------------------------------------- 1 | filter(["Watchmen", "Batman"], function (title) { 2 | return title.includes("Watch"); 3 | }); 4 | // → ["Watchmen"] 5 | -------------------------------------------------------------------------------- /src/chapter03/filter_impl.js: -------------------------------------------------------------------------------- 1 | function filter(coll, f) { 2 | var res = []; 3 | for(var i = 0; i < coll.length; i++) { // <1> 4 | if(f(coll[i])) { 5 | res.push(coll[i]); 6 | } 7 | } 8 | return res; 9 | } 10 | -------------------------------------------------------------------------------- /src/chapter03/get-impl-test.js: -------------------------------------------------------------------------------- 1 | get(catalogData, ["booksByIsbn", "978-1779501127", "title"]); 2 | // → "Watchmen" 3 | 4 | -------------------------------------------------------------------------------- /src/chapter03/get_impl.js: -------------------------------------------------------------------------------- 1 | function get(m, path) { 2 | var res = m; 3 | for(var i = 0; i < path.length; i++) { // <1> 4 | var key = path[i]; 5 | res = res[key]; 6 | } 7 | return res; 8 | } 9 | -------------------------------------------------------------------------------- /src/chapter03/is-librarian-test.js: -------------------------------------------------------------------------------- 1 | isLibrarian(userManagementData, "franck@gmail.com"); 2 | // → true 3 | -------------------------------------------------------------------------------- /src/chapter03/is-librarian.js: -------------------------------------------------------------------------------- 1 | function isLibrarian(userManagement, email) { 2 | return _.has(_.get(userManagement, "librariansByEmail"), email); 3 | } 4 | -------------------------------------------------------------------------------- /src/chapter03/is-vip.js: -------------------------------------------------------------------------------- 1 | function isVIPMember(userManagement, email) { 2 | return _.get(userManagement, ["membersByEmail", email, "isVIP"]) == true; 3 | } 4 | 5 | -------------------------------------------------------------------------------- /src/chapter03/map-author-names.js: -------------------------------------------------------------------------------- 1 | _.map(["alan-moore", "dave-gibbons"], 2 | function(authorId) { 3 | return _.get(catalogData, ["authorsById", authorId, "name"]); 4 | }); 5 | // → [ "Alan Moore", "Dave Gibbons"] 6 | -------------------------------------------------------------------------------- /src/chapter03/map_impl.js: -------------------------------------------------------------------------------- 1 | function map(coll, f) { 2 | var res = []; 3 | for(var i = 0; i < coll.length; i++) { // <1> 4 | res[i] = f(coll[i]); 5 | } 6 | return res; 7 | } 8 | -------------------------------------------------------------------------------- /src/chapter03/search-book-by-title.js: -------------------------------------------------------------------------------- 1 | function searchBooksByTitle(catalogData, query) { 2 | var allBooks = _.values(_.get(catalogData, "booksByIsbn")); 3 | var matchingBooks = _.filter(allBooks, function(book) { 4 | return _.get(book, "title").includes(query); // <1> 5 | }); 6 | 7 | var bookInfos = _.map(matchingBooks, function(book) { 8 | return bookInfo(catalogData, book); 9 | }); 10 | return bookInfos; 11 | } 12 | 13 | -------------------------------------------------------------------------------- /src/chapter03/search-book-json-test.js: -------------------------------------------------------------------------------- 1 | searchBooksByTitleJSON(libraryData, "Wat"); 2 | 3 | -------------------------------------------------------------------------------- /src/chapter03/search-book-json.js: -------------------------------------------------------------------------------- 1 | function searchBooksByTitleJSON(libraryData, query) { 2 | var results = searchBooksByTitle(_.get(libraryData, "catalog"), query); 3 | var resultsJSON = JSON.stringify(results); 4 | return resultsJSON; 5 | } 6 | -------------------------------------------------------------------------------- /src/chapter03/search-books-test.js: -------------------------------------------------------------------------------- 1 | searchBooksByTitle(catalogData, "Wat"); 2 | //[ 3 | // { 4 | // "authorNames": [ 5 | // "Alan Moore", 6 | // "Dave Gibbons" 7 | // ], 8 | // "isbn": "978-1779501127", 9 | // "title": "Watchmen" 10 | // } 11 | //] 12 | -------------------------------------------------------------------------------- /src/chapter03/search-watchmen.js: -------------------------------------------------------------------------------- 1 | Library.searchBooksByTitleJSON(libraryData, "Watchmen"); 2 | // → "[{\"title\":\"Watchmen\",\"isbn\":\"978-1779501127\", 3 | // → \"authorNames\":[\"Alan Moore\",\"Dave Gibbons\"]}]" 4 | -------------------------------------------------------------------------------- /src/chapter03/user-management-data.js: -------------------------------------------------------------------------------- 1 | var userManagementData = { 2 | "librariansByEmail": { 3 | "franck@gmail.com" : { 4 | "email": "franck@gmail.com", 5 | "encryptedPassword": "bXlwYXNzd29yZA==" //<1> 6 | } 7 | }, 8 | "membersByEmail": { 9 | "samantha@gmail.com": { 10 | "email": "samantha@gmail.com", 11 | "encryptedPassword": "c2VjcmV0", //<2> 12 | "isBlocked": false, 13 | "bookLendings": [ 14 | { 15 | "bookItemId": "book-item-1", 16 | "bookIsbn": "978-1779501127", 17 | "lendingDate": "2020-04-23" 18 | } 19 | ] 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/chapter03/user-management.js: -------------------------------------------------------------------------------- 1 | class UserManagement { 2 | isLibrarian(userManagement, email) { 3 | return _.has(_.get(userManagement, "librariansByEmail"), email); 4 | } 5 | 6 | isVIPMember(userManagement, email) { 7 | return _.get(userManagement, ["membersByEmail", email, "isVIP"]) == true; 8 | } 9 | 10 | isSuperMember(userManagement, email) { 11 | return _.get(userManagement, ["membersByEmail", email, "isSuper"]) == true; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/chapter03/watchmen-book-path.js: -------------------------------------------------------------------------------- 1 | _.get(catalogData, ["booksByIsbn", "978-1779501127", "title"]) 2 | // → "Watchmen" 3 | 4 | -------------------------------------------------------------------------------- /src/chapter03/watchmen-do.java: -------------------------------------------------------------------------------- 1 | Map watchmen = Map.of( 2 | "isbn", "978-1779501127", 3 | "title", "Watchmen", 4 | "publicationYear", 1987, 5 | "authors", List.of("alan-moore", "dave-gibbons"), 6 | "bookItems", List.of( 7 | Map.of( 8 | "id", "book-item-1", 9 | "libId", "nyc-central-lib", 10 | "isLent", true 11 | ), 12 | Map.of ( 13 | "id", "book-item-2", 14 | "libId", "nyc-central-lib", 15 | "isLent", false 16 | ) 17 | ) 18 | ); 19 | 20 | -------------------------------------------------------------------------------- /src/chapter03/watchmen-do.js: -------------------------------------------------------------------------------- 1 | var watchmenBook = { 2 | "isbn": "978-1779501127", 3 | "title": "Watchmen", 4 | "publicationYear": 1987, 5 | "authors": ["alan-moore", "dave-gibbons"], 6 | "bookItems": [ 7 | { 8 | "id": "book-item-1", 9 | "libId": "nyc-central-lib", 10 | "isLent": true 11 | }, 12 | { 13 | "id": "book-item-2", 14 | "libId": "nyc-central-lib", 15 | "isLent": false 16 | } 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /src/chapter03/watchmen-oo.js: -------------------------------------------------------------------------------- 1 | class Book { 2 | isbn; 3 | title; 4 | publicationYear; 5 | authors; 6 | bookItems; 7 | constructor(isbn, title, publicationYear, authors, bookItems) { 8 | this.isbn = isbn; 9 | this.title = title; 10 | this.publicationYear = publicationYear; 11 | this.authors = authors; 12 | this.bookItems = bookItems; 13 | } 14 | } 15 | 16 | class BookItem { 17 | id; 18 | libId; 19 | isLent; 20 | constructor(id, libId, isLent) { 21 | this.id = id; 22 | this.libId = libId; 23 | this.isLent = isLent; 24 | } 25 | } 26 | 27 | var watchmenBook = new Book("978-1779501127", 28 | "Watchmen", 29 | 1987, 30 | ["alan-moore", "dave-gibbons"], 31 | [new BookItem("book-item-1", "nyc-central-lib", true), 32 | new BookItem("book-item-2", "nyc-central-lib", false)]); 33 | -------------------------------------------------------------------------------- /src/chapter03/watchmen-search-result.js: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Watchmen", 3 | "isbn": "978-1779501127", 4 | "authorNames": [ 5 | "Alan Moore", 6 | "Dave Gibbons", 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /src/chapter03/watchmen-title.java: -------------------------------------------------------------------------------- 1 | ((String)watchmen.get("title")).toUpperCase() 2 | -------------------------------------------------------------------------------- /src/chapter03/watchmen.js: -------------------------------------------------------------------------------- 1 | { 2 | "isbn": "978-1779501127", 3 | "title": "Watchmen", 4 | "publicationYear": 1987, 5 | "authors": ["alan-moore", "dave-gibbons"], 6 | "bookItems": [ 7 | { 8 | "id": "book-item-1", 9 | "libId": "nyc-central-lib", 10 | "isLent": true 11 | }, 12 | { 13 | "id": "book-item-2", 14 | "libId": "nyc-central-lib", 15 | "isLent": false 16 | } 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /src/chapter04/add-member-library.js: -------------------------------------------------------------------------------- 1 | Library.addMember = function(library, member) { 2 | var currentUserManagement = _.get(library, "userManagement"); 3 | var nextUserManagement = UserManagement.addMember(currentUserManagement, member); 4 | var nextLibrary = _.set(library, "userManagement", nextUserManagement); 5 | return nextLibrary; 6 | }; 7 | -------------------------------------------------------------------------------- /src/chapter04/add-member-system.js: -------------------------------------------------------------------------------- 1 | class System { 2 | addMember(member) { 3 | var previous = SystemState.get(); 4 | var next = Library.addMember(previous, member); 5 | SystemState.commit(previous, next); // <1> 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/chapter04/add-member.js: -------------------------------------------------------------------------------- 1 | UserManagement.addMember = function(userManagement, member) { 2 | var email = _.get(member, "email"); 3 | var infoPath = ["membersByEmail", email]; 4 | if(_.has(userManagement, infoPath)) { // <1> 5 | throw "Member already exists."; 6 | } 7 | var nextUserManagement = _.set(userManagement, // <2> 8 | infoPath, 9 | member); 10 | return nextUserManagement; 11 | }; 12 | 13 | Library.addMember = function(library, member) { 14 | var currentUserManagement = _.get(library, "userManagement"); 15 | var nextUserManagement = UserManagement.addMember(currentUserManagement, member); 16 | var nextLibrary = _.set(library, "userManagement", nextUserManagement); // <3> 17 | return nextLibrary; 18 | }; 19 | -------------------------------------------------------------------------------- /src/chapter04/lodash-config.js: -------------------------------------------------------------------------------- 1 | _ = fp.convert({ 2 | "cap": false, 3 | "curry": false, 4 | "fixed": false, 5 | "immutable": true, 6 | "rearg": false 7 | }); 8 | -------------------------------------------------------------------------------- /src/chapter04/modify-shared-data.js: -------------------------------------------------------------------------------- 1 | var books = { 2 | "978-1779501127": { 3 | "isbn": "978-1779501127", 4 | "title": "Watchmen", 5 | "publicationYear": 1987, 6 | "authorIds": ["alan-moore", 7 | "dave-gibbons"] 8 | } 9 | }; 10 | 11 | var nextBooks = _.set(books, ["978-1779501127", "publicationYear"], 1986) 12 | 13 | console.log("Before:", nextBooks["978-1779501127"]["authorIds"][1]); 14 | 15 | books["978-1779501127"]["authorIds"][1] = "dave-chester-gibbons"; 16 | 17 | console.log("After:", nextBooks["978-1779501127"]["authorIds"][1]); 18 | // → Before: dave-gibbons 19 | // → After: dave-chester-gibbons 20 | -------------------------------------------------------------------------------- /src/chapter04/structural-sharing-impl.js: -------------------------------------------------------------------------------- 1 | function setImmutable(map, path, v) { 2 | var modifiedNode = v; 3 | var k = path[0]; 4 | var restOfPath = path.slice(1); 5 | if (restOfPath.length > 0) { 6 | modifiedNode = setImmutable(map[k], restOfPath, v); 7 | } 8 | var res = Object.assign({}, map); // <1> 9 | res[k] = modifiedNode; 10 | return res; 11 | } 12 | -------------------------------------------------------------------------------- /src/chapter04/system-data-memo.js: -------------------------------------------------------------------------------- 1 | class SystemState { 2 | systemData; 3 | previousSystemData; 4 | 5 | get() { 6 | return this.systemData; 7 | } 8 | 9 | commit(previous, next) { 10 | var systemDataBeforeUpdate = this.systemData; 11 | if(!Consistency.validate(previous, next)) { 12 | throw "The system data to be committed is not valid!"; 13 | } 14 | this.systemData = next; 15 | this.previousSystemData = systemDataBeforeUpdate; 16 | } 17 | 18 | undoLastMutation() { 19 | this.systemData = this.previousSystemData; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/chapter04/system-data-single-thread.js: -------------------------------------------------------------------------------- 1 | class SystemState { 2 | systemState; 3 | 4 | get() { 5 | return this.systemState; 6 | } 7 | 8 | commit(previous, next) { 9 | this.systemState = next; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/chapter04/update-publication-year.js: -------------------------------------------------------------------------------- 1 | var nextLibraryData = _.set(libraryData, 2 | ["catalog", "booksByIsbn", 3 | "978-1779501127", "publicationYear"], 4 | 1986); 5 | -------------------------------------------------------------------------------- /src/chapter04/validate.js: -------------------------------------------------------------------------------- 1 | SystemState.commit = function(previous, next) { 2 | if(!SystemValidity.validate(previous, next)) { // not implemented for now 3 | throw "The system data to be committed is not valid!"; 4 | }; 5 | this.systemData = next; 6 | }; 7 | -------------------------------------------------------------------------------- /src/chapter05/ax1.json: -------------------------------------------------------------------------------- 1 | { 2 | "a": { 3 | "x": 1 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /src/chapter05/ax1y2.json: -------------------------------------------------------------------------------- 1 | { 2 | "a": { 3 | "x": 1, 4 | "y": 2, 5 | } 6 | } 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/chapter05/ax2.json: -------------------------------------------------------------------------------- 1 | { 2 | "a": { 3 | "x": 2 4 | } 5 | } 6 | 7 | -------------------------------------------------------------------------------- /src/chapter05/ay2.json: -------------------------------------------------------------------------------- 1 | { 2 | "a": { 3 | "y": 2 4 | } 5 | } 6 | 7 | -------------------------------------------------------------------------------- /src/chapter05/consistency.js: -------------------------------------------------------------------------------- 1 | class SystemConsistency { 2 | static threeWayMerge(current, previous, next) { 3 | var previousToCurrent = diff(previous, current); // <1> 4 | var previousToNext = diff(previous, next); 5 | if(!havePathInCommon(previousToCurrent, previousToNext)) { 6 | return _.merge(current, previousToNext); 7 | } 8 | throw "Conflicting concurrent mutations."; 9 | } 10 | static reconcile(current, previous, next) { 11 | if(current == previous) { 12 | return next; // <2> 13 | } 14 | return SystemConsistency.threeWayMerge(current, 15 | previous, 16 | next); 17 | } 18 | } 19 | 20 | -------------------------------------------------------------------------------- /src/chapter05/data-diff-diff.js: -------------------------------------------------------------------------------- 1 | function diffObjects(data1, data2) { 2 | var emptyObject = _.isArray(data1) ? [] : {}; // <1> 3 | if(data1 == data2) { 4 | return emptyObject; 5 | } 6 | var keys = _.union(_.keys(data1), _.keys(data2)); // <2> 7 | return _.reduce(keys, 8 | function (acc, k) { 9 | var res = diff( 10 | _.get(data1, k), 11 | _.get(data2, k)); 12 | if((_.isObject(res) && _.isEmpty(res)) || // <3> <4> 13 | (res == "no-diff")) { // <5> 14 | return acc; 15 | } 16 | return _.set(acc, [k], res); 17 | }, 18 | emptyObject); 19 | } 20 | 21 | function diff(data1, data2) { 22 | if(_.isObject(data1) && _.isObject(data2)) { // <4> 23 | return diffObjects(data1, data2); 24 | } 25 | if(data1 !== data2) { 26 | return data2; 27 | } 28 | return "no-diff"; // <5> 29 | } 30 | 31 | 32 | -------------------------------------------------------------------------------- /src/chapter05/data-diff-example.js: -------------------------------------------------------------------------------- 1 | var data1 = { 2 | "a": { 3 | "x": 1, 4 | "y": [2, 3], 5 | "z": 4 6 | } 7 | }; 8 | 9 | var data2 = { 10 | "a": { 11 | "x": 2, 12 | "y": [2, 4], 13 | "z": 4 14 | } 15 | } 16 | 17 | diff(data1, data2); 18 | //{ 19 | // "a": { 20 | // "x": 2, 21 | // "y": [ 22 | // undefined, 23 | // 4 24 | // ] 25 | // } 26 | //} 27 | -------------------------------------------------------------------------------- /src/chapter05/diff-previous-current.js: -------------------------------------------------------------------------------- 1 | diff(previous, current); 2 | //{ 3 | // "authorsById": { 4 | // "dave-gibbons": { 5 | // "name": "David Chester Gibbons", 6 | // } 7 | // }, 8 | // "catalog": { 9 | // "booksByIsbn": { 10 | // "978-1779501127": { 11 | // "title": "The Watchmen" 12 | // } 13 | // } 14 | // } 15 | //} 16 | // 17 | -------------------------------------------------------------------------------- /src/chapter05/diff-previous-next.js: -------------------------------------------------------------------------------- 1 | diff(previous, next); 2 | //{ 3 | // "catalog": { 4 | // "booksByIsbn": { 5 | // "978-1779501127": { 6 | // "publicationYear": 1986 7 | // } 8 | // } 9 | // } 10 | //} 11 | -------------------------------------------------------------------------------- /src/chapter05/leaves-previous-current.js: -------------------------------------------------------------------------------- 1 | informationPaths(diff(previous, current)); 2 | // [ 3 | // [ 4 | // "catalog", 5 | // "booksByIsbn", 6 | // "978-1779501127", 7 | // "title" 8 | // ], 9 | // [ 10 | // "authorsById", 11 | // "dave-gibbons", 12 | // "name" 13 | // ] 14 | //] 15 | -------------------------------------------------------------------------------- /src/chapter05/leaves-previous-next.js: -------------------------------------------------------------------------------- 1 | informationPaths(diff(previous, next)); 2 | // → ["catalog.booksByIsbn.978-1779501127.publicationYear"] 3 | -------------------------------------------------------------------------------- /src/chapter05/leaves.js: -------------------------------------------------------------------------------- 1 | function informationPaths (obj, path = []) { 2 | return _.reduce(obj, 3 | function(acc, v, k) { 4 | if (_.isObject(v)) { 5 | return _.concat(acc, 6 | informationPaths(v, 7 | _.concat(path, k))); 8 | } 9 | return _.concat(acc, [_.concat(path, k)]); // <1> 10 | }, 11 | []); 12 | } 13 | -------------------------------------------------------------------------------- /src/chapter05/library-data.js: -------------------------------------------------------------------------------- 1 | var library = { 2 | "catalog": { 3 | "booksByIsbn": { 4 | "978-1779501127": { 5 | "isbn": "978-1779501127", 6 | "title": "Watchmen", 7 | "publicationYear": 1987, 8 | "authorIds": ["alan-moore", "dave-gibbons"] 9 | } 10 | }, 11 | "authorsById": { 12 | "alan-moore": { 13 | "name": "Alan Moore", 14 | "bookIsbns": ["978-1779501127"] 15 | }, 16 | "dave-gibbons": { 17 | "name": "Dave Gibbons", 18 | "bookIsbns": ["978-1779501127"] 19 | } 20 | } 21 | } 22 | }; 23 | -------------------------------------------------------------------------------- /src/chapter05/patch.js: -------------------------------------------------------------------------------- 1 | _.merge(current, (diff(previous, next))); 2 | //{ 3 | // "authorsById": { 4 | // "dave-gibbons": { 5 | // "name": "David Chester Gibbons" 6 | // } 7 | // }, 8 | // "catalog": { 9 | // "authorsById": { 10 | // "alan-moore": { 11 | // "bookIsbns": ["978-1779501127"] 12 | // "name": "Alan Moore" 13 | // }, 14 | // "dave-gibbons": { 15 | // "bookIsbns": ["978-1779501127"], 16 | // "name": "Dave Gibbons" 17 | // }, 18 | // }, 19 | // "booksByIsbn": { 20 | // "978-1779501127": { 21 | // "authorIds": ["alan-moore", "dave-gibbons"], 22 | // "isbn": "978-1779501127", 23 | // "publicationYear": 1986, 24 | // "title": "The Watchmen" 25 | // } 26 | // } 27 | // } 28 | //} 29 | -------------------------------------------------------------------------------- /src/chapter05/paths-in-common.js: -------------------------------------------------------------------------------- 1 | function havePathInCommon(diff1, diff2) { 2 | return !_.isEmpty(_.intersection(informationPaths(diff1), 3 | informationPaths(diff2))); 4 | } 5 | 6 | -------------------------------------------------------------------------------- /src/chapter05/reduce_impl.js: -------------------------------------------------------------------------------- 1 | function reduce(coll, f, initVal) { 2 | var currentRes = initVal; 3 | for (var i = 0; i < coll.length; i++) { // <1> 4 | currentRes = f(currentRes, coll[i]) 5 | } 6 | return currentRes; 7 | } 8 | -------------------------------------------------------------------------------- /src/chapter05/reduce_numbers.js: -------------------------------------------------------------------------------- 1 | _.reduce([1, 2, 3], function(res, elem) { 2 | return res + elem; 3 | }, 0); 4 | // → 6 5 | 6 | -------------------------------------------------------------------------------- /src/chapter05/system-data-single-thread.js: -------------------------------------------------------------------------------- 1 | class SystemState { 2 | systemData; 3 | 4 | get() { 5 | return this.systemData; 6 | } 7 | 8 | set(_systemData) { 9 | this.systemData = _systemData; 10 | } 11 | 12 | commit(previous, next) { 13 | var nextSystemData = SystemConsistency.reconcile(this.systemData, // <1> 14 | previous, 15 | next); 16 | if(!SystemValidity.validate(previous, nextSystemData)) { 17 | throw "The system data to be committed is not valid!"; 18 | }; 19 | this.systemData = nextSystemData; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/chapter05/test-reduce.js: -------------------------------------------------------------------------------- 1 | reduce([1, 2, 3], function(res, elem) { 2 | return res + elem; 3 | }, 0); 4 | // → 6 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/chapter05/three-way-merge-example.js: -------------------------------------------------------------------------------- 1 | var previous = library; 2 | 3 | var next = _.set( 4 | library, 5 | ["catalog", "booksByIsbn", "978-1779501127", "publicationYear"], 6 | 1986); 7 | 8 | var libraryWithUpdatedTitle = _.set( 9 | library, 10 | ["catalog", "booksByIsbn", "978-1779501127", "title"], 11 | "The Watchmen"); 12 | var current = _.set( 13 | libraryWithUpdatedTitle, 14 | ["catalog", "authorsById", "dave-gibbons", "name"], 15 | "David Chester Gibbons"); 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/chapter06/add-member.js: -------------------------------------------------------------------------------- 1 | System.addMember = function(systemState, member) { 2 | var previous = systemState.get(); 3 | var next = Library.addMember(previous, member); 4 | systemState.commit(previous, next); 5 | }; 6 | 7 | Library.addMember = function(library, member) { 8 | var currentUserManagement = _.get(library, "userManagement"); 9 | var nextUserManagement = UserManagement.addMember(currentUserManagement, member); 10 | var nextLibrary = _.set(library, "userManagement", nextUserManagement); 11 | return nextLibrary; 12 | }; 13 | 14 | UserManagement.addMember = function(userManagement, member) { 15 | var email = _.get(member, "email"); 16 | var infoPath = ["membersByEmail", email]; 17 | if(_.has(userManagement, infoPath)) { 18 | throw "Member already exists."; 19 | } 20 | var nextUserManagement = _.set(userManagement, 21 | infoPath, 22 | member); 23 | return nextUserManagement; 24 | }; 25 | -------------------------------------------------------------------------------- /src/chapter06/author-names-more-tests.js: -------------------------------------------------------------------------------- 1 | _.isEqual(Catalog.authorNames({}, []), []); 2 | _.isEqual(Catalog.authorNames({}, ["alan-moore"]), [undefined]); 3 | 4 | _.isEqual(Catalog.authorNames(catalogData, ["alan-moore", "albert-einstein"]), 5 | ["Alan Moore", undefined]); 6 | _.isEqual(Catalog.authorNames(catalogData, []), []); 7 | _.isEqual(Catalog.authorNames(catalogData, ["albert-einstein"]), [undefined]); 8 | -------------------------------------------------------------------------------- /src/chapter06/author-names-test.js: -------------------------------------------------------------------------------- 1 | var catalogData = { 2 | "authorsById": { 3 | "alan-moore": { 4 | "name": "Alan Moore" 5 | }, 6 | "dave-gibbons": { 7 | "name": "Dave Gibbons" 8 | } 9 | } 10 | }; 11 | 12 | _.isEqual(Catalog.authorNames(catalogData, []), []); 13 | _.isEqual(Catalog.authorNames(catalogData, ["alan-moore"]), ["Alan Moore"]); 14 | _.isEqual(Catalog.authorNames(catalogData, ["alan-moore", "dave-gibbons"]), 15 | ["Alan Moore", "Dave Gibbons"]); 16 | -------------------------------------------------------------------------------- /src/chapter06/author-names.js: -------------------------------------------------------------------------------- 1 | Catalog.authorNames = function (catalogData, authorIds) { 2 | return _.map(authorIds, function(authorId) { 3 | return _.get(catalogData, ["authorsById", authorId, "name"]); 4 | }); 5 | }; 6 | -------------------------------------------------------------------------------- /src/chapter06/book-info-pass-simple.js: -------------------------------------------------------------------------------- 1 | var catalogData = { 2 | "authorsById": { 3 | "alan-moore": { 4 | "name": "Alan Moore" 5 | }, 6 | "dave-gibbons": { 7 | "name": "Dave Gibbons" 8 | } 9 | } 10 | }; 11 | 12 | var book = { 13 | "isbn": "978-1779501127", 14 | "title": "Watchmen", 15 | "publicationYear": 1987, 16 | "authorIds": ["alan-moore", "dave-gibbons"] 17 | }; 18 | 19 | var expectedResult = { 20 | "authorNames": ["Alan Moore", "Dave Gibbons"], 21 | "isbn": "978-1779501127", 22 | "title": "Watchmen", 23 | }; 24 | 25 | var result = Catalog.bookInfo(catalogData, book); 26 | 27 | _.isEqual(result, expectedResult); 28 | -------------------------------------------------------------------------------- /src/chapter06/book-info.js: -------------------------------------------------------------------------------- 1 | Catalog.bookInfo = function (catalogData, book) { 2 | return { 3 | "title": _.get(book, "title"), 4 | "isbn": _.get(book, "isbn"), 5 | "authorNames": Catalog.authorNames(catalogData, _.get(book, "authorIds")) 6 | }; 7 | }; 8 | -------------------------------------------------------------------------------- /src/chapter06/catalog-data-min.js: -------------------------------------------------------------------------------- 1 | var catalogData = { 2 | "authorsById": { 3 | "alan-moore": { 4 | "name": "Alan Moore" 5 | }, 6 | "dave-gibbons": { 7 | "name": "Dave Gibbons" 8 | } 9 | } 10 | }; 11 | -------------------------------------------------------------------------------- /src/chapter06/catalog-data.js: -------------------------------------------------------------------------------- 1 | var catalogData = { 2 | "booksByIsbn": { 3 | "978-1779501127": { 4 | "isbn": "978-1779501127", 5 | "title": "Watchmen", 6 | "publicationYear": 1987, 7 | "authorIds": ["alan-moore", "dave-gibbons"], 8 | "bookItems": [ 9 | { 10 | "id": "book-item-1", 11 | "libId": "nyc-central-lib", 12 | "isLent": true 13 | }, 14 | { 15 | "id": "book-item-2", 16 | "libId": "nyc-central-lib", 17 | "isLent": false 18 | } 19 | ] 20 | } 21 | }, 22 | "authorsById": { 23 | "alan-moore": { 24 | "name": "Alan Moore", 25 | "bookIsbns": ["978-1779501127"] 26 | }, 27 | "dave-gibbons": { 28 | "name": "Dave Gibbons", 29 | "bookIsbns": ["978-1779501127"] 30 | } 31 | } 32 | }; 33 | -------------------------------------------------------------------------------- /src/chapter06/catalog-search-lower.js: -------------------------------------------------------------------------------- 1 | _.isEqual(Catalog.searchBooksByTitle(catalogData, "watchmen"), 2 | [bookInfo]); 3 | -------------------------------------------------------------------------------- /src/chapter06/catalog-search-test.js: -------------------------------------------------------------------------------- 1 | var catalogData = { 2 | "booksByIsbn": { 3 | "978-1779501127": { 4 | "isbn": "978-1779501127", 5 | "title": "Watchmen", 6 | "publicationYear": 1987, 7 | "authorIds": ["alan-moore", 8 | "dave-gibbons"] 9 | } 10 | }, 11 | "authorsById": { 12 | "alan-moore": { 13 | "name": "Alan Moore", 14 | "bookIsbns": ["978-1779501127"] 15 | }, 16 | "dave-gibbons": { 17 | "name": "Dave Gibbons", 18 | "bookIsbns": ["978-1779501127"] 19 | } 20 | } 21 | }; 22 | 23 | var bookInfo = { 24 | "isbn": "978-1779501127", 25 | "title": "Watchmen", 26 | "authorNames": ["Alan Moore", 27 | "Dave Gibbons"] 28 | }; 29 | 30 | _.isEqual(Catalog.searchBooksByTitle(catalogData, "Watchmen"), [bookInfo]); 31 | _.isEqual(Catalog.searchBooksByTitle(catalogData, "Batman"), []); 32 | -------------------------------------------------------------------------------- /src/chapter06/catalog-search.js: -------------------------------------------------------------------------------- 1 | Catalog.searchBooksByTitle = function(catalogData, query) { 2 | var allBooks = _.get(catalogData, "booksByIsbn"); 3 | var matchingBooks = _.filter(allBooks, function(book) { 4 | return _.get(book, "title").includes(query); 5 | }); 6 | var bookInfos = _.map(matchingBooks, function(book) { 7 | return Catalog.bookInfo(catalogData, book); 8 | }); 9 | return bookInfos; 10 | }; 11 | -------------------------------------------------------------------------------- /src/chapter06/fix-search-book.js: -------------------------------------------------------------------------------- 1 | Catalog.searchBooksByTitle = function(catalogData, query) { 2 | var allBooks = _.get(catalogData, "booksByIsbn"); 3 | var queryLowerCased = query.toLowerCase(); // <1> 4 | var matchingBooks = _.filter(allBooks, function(book) { 5 | return _.get(book, "title") 6 | .toLowerCase() // <2> 7 | .includes(queryLowerCased); 8 | }); 9 | var bookInfos = _.map(matchingBooks, function(book) { 10 | return Catalog.bookInfo(catalogData, book); 11 | }); 12 | return bookInfos; 13 | }; 14 | 15 | -------------------------------------------------------------------------------- /src/chapter06/is-equal-2.js: -------------------------------------------------------------------------------- 1 | _.isEqual({ 2 | "name": "Alan Moore", 3 | "bookIsbns": ["978-1779501127"] 4 | }, { 5 | "name": "Alan Moore", 6 | "bookIsbns": ["bad-isbn"] 7 | }); 8 | // → false 9 | 10 | -------------------------------------------------------------------------------- /src/chapter06/is-equal.js: -------------------------------------------------------------------------------- 1 | _.isEqual({ 2 | "name": "Alan Moore", 3 | "bookIsbns": ["978-1779501127"] 4 | }, { 5 | "name": "Alan Moore", 6 | "bookIsbns": ["978-1779501127"] 7 | }); 8 | // → true 9 | 10 | -------------------------------------------------------------------------------- /src/chapter06/library-search.js: -------------------------------------------------------------------------------- 1 | Library.searchBooksByTitleJSON = function (libraryData, query) { 2 | var catalogData = _.get(libraryData, "catalog"); 3 | var results = Catalog.searchBooksByTitle(catalogData, query); 4 | var resultsJSON = JSON.stringify(results); 5 | return resultsJSON; 6 | }; 7 | 8 | -------------------------------------------------------------------------------- /src/chapter06/string-deserialize.js: -------------------------------------------------------------------------------- 1 | var myString = "{\"publicationYear\":1987,\"title\":\"Watchmen\"}"; 2 | var myData = JSON.parse(myString); 3 | _.get(myData, "title"); 4 | // → "Watchmen" 5 | -------------------------------------------------------------------------------- /src/chapter06/system-add-member.js: -------------------------------------------------------------------------------- 1 | System.addMember = function(systemState, member) { 2 | var previous = systemState.get(); 3 | var next = Library.addMember(previous, member); 4 | systemState.commit(previous, next); 5 | }; 6 | -------------------------------------------------------------------------------- /src/chapter06/system-state.js: -------------------------------------------------------------------------------- 1 | class SystemState { 2 | systemState; 3 | 4 | get() { 5 | return this.systemState; 6 | } 7 | 8 | commit(previous, next) { 9 | this.systemState = next; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/chapter06/test-add-member-empty.js: -------------------------------------------------------------------------------- 1 | var member = { 2 | "email": "jessie@gmail.com", 3 | "password": "my-secret" 4 | }; 5 | 6 | var userManagementStateBefore = {}; 7 | 8 | var expectedUserManagementStateAfter = { 9 | "membersByEmail": { 10 | "jessie@gmail.com": { 11 | "email": "jessie@gmail.com", 12 | "password": "my-secret" 13 | } 14 | } 15 | }; 16 | 17 | var result = UserManagement.addMember(userManagementStateBefore, member); 18 | _.isEqual(result, expectedUserManagementStateAfter); 19 | -------------------------------------------------------------------------------- /src/chapter06/test-add-member-error.js: -------------------------------------------------------------------------------- 1 | var jessie = { 2 | "email": "jessie@gmail.com", 3 | "password": "my-secret" 4 | }; 5 | 6 | var userManagementStateBefore = { 7 | "membersByEmail": { 8 | "jessie@gmail.com": { 9 | "email": "jessie@gmail.com", 10 | "password": "my-secret" 11 | } 12 | } 13 | }; 14 | 15 | var expectedException = "Member already exists."; 16 | var exceptionInMutation; 17 | 18 | try { 19 | UserManagement.addMember(userManagementStateBefore, jessie); 20 | } catch (e) { 21 | exceptionInMutation = e; 22 | } 23 | 24 | _.isEqual(exceptionInMutation, expectedException); 25 | -------------------------------------------------------------------------------- /src/chapter06/test-add-member-initial-data.js: -------------------------------------------------------------------------------- 1 | var jessie = { 2 | "email": "jessie@gmail.com", 3 | "password": "my-secret" 4 | }; 5 | 6 | var franck = { 7 | "email": "franck@gmail.com", 8 | "password": "my-top-secret" 9 | }; 10 | 11 | var userManagementStateBefore = { 12 | "membersByEmail": { 13 | "franck@gmail.com": { 14 | "email": "franck@gmail.com", 15 | "password": "my-top-secret" 16 | } 17 | } 18 | }; 19 | 20 | var expectedUserManagementStateAfter = { 21 | "membersByEmail": { 22 | "jessie@gmail.com": { 23 | "email": "jessie@gmail.com", 24 | "password": "my-secret" 25 | }, 26 | "franck@gmail.com": { 27 | "email": "franck@gmail.com", 28 | "password": "my-top-secret" 29 | } 30 | } 31 | }; 32 | 33 | var result = UserManagement.addMember(userManagementStateBefore, jessie); 34 | _.isEqual(result, expectedUserManagementStateAfter); 35 | -------------------------------------------------------------------------------- /src/chapter06/test-case.js: -------------------------------------------------------------------------------- 1 | var dataIn = { 2 | // input 3 | }; 4 | 5 | var dataOut = { 6 | // expected output 7 | }; 8 | 9 | _.isEqual(f(dataIn), dataOut); 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/chapter06/test-lib-add-member.js: -------------------------------------------------------------------------------- 1 | var jessie = { 2 | "email": "jessie@gmail.com", 3 | "password": "my-secret" 4 | }; 5 | 6 | var franck = { 7 | "email": "franck@gmail.com", 8 | "password": "my-top-secret" 9 | }; 10 | 11 | var libraryStateBefore = { 12 | "userManagement": { 13 | "membersByEmail": { 14 | "franck@gmail.com": { 15 | "email": "franck@gmail.com", 16 | "password": "my-top-secret" 17 | } 18 | } 19 | } 20 | }; 21 | 22 | var expectedLibraryStateAfter = { 23 | "userManagement": { 24 | "membersByEmail": { 25 | "jessie@gmail.com": { 26 | "email": "jessie@gmail.com", 27 | "password": "my-secret" 28 | }, 29 | "franck@gmail.com": { 30 | "email": "franck@gmail.com", 31 | "password": "my-top-secret" 32 | } 33 | } 34 | } 35 | }; 36 | 37 | var result = Library.addMember(libraryStateBefore, jessie); 38 | _.isEqual(result, expectedLibraryStateAfter); 39 | -------------------------------------------------------------------------------- /src/chapter06/two-strings-same-data.js: -------------------------------------------------------------------------------- 1 | var stringA = "{\"title\":\"Watchmen\",\"publicationYear\":1987}"; 2 | var stringB = "{\"publicationYear\":1987,\"title\":\"Watchmen\"}"; 3 | -------------------------------------------------------------------------------- /src/chapter07/book-info-basic-schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "object", 3 | "required": ["title"], 4 | "properties": { 5 | "title": {"type": "string"}, 6 | "publishers": { 7 | "type": "array", 8 | "items": {"type": "string"} 9 | }, 10 | "number_of_pages": {"type": "integer"}, 11 | "weight": {"type": "string"}, 12 | "physical_format": {"type": "string"}, 13 | "subjects": { 14 | "type": "array", 15 | "items": {"type": "string"} 16 | }, 17 | "isbn_13": { 18 | "type": "array", 19 | "items": {"type": "string"} 20 | }, 21 | "isbn_10": { 22 | "type": "array", 23 | "items": {"type": "string"} 24 | }, 25 | "publish_date": {"type": "string"}, 26 | "physical_dimensions": {"type": "string"} 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/chapter07/book-info-example.json: -------------------------------------------------------------------------------- 1 | { 2 | "publishers": [ 3 | "DC Comics" 4 | ], 5 | "number_of_pages": 334, 6 | "weight": "1.4 pounds", 7 | "physical_format": "Paperback", 8 | "subjects": [ 9 | "Graphic Novels", 10 | "Comics & Graphic Novels", 11 | "Fiction", 12 | "Fantastic fiction" 13 | ], 14 | "isbn_13": [ 15 | "9780930289232" 16 | ], 17 | "title": "Watchmen", 18 | "isbn_10": [ 19 | "0930289234" 20 | ], 21 | "publish_date": "April 1, 1995", 22 | "physical_dimensions": "10.1 x 6.6 x 0.8 inches" 23 | } 24 | -------------------------------------------------------------------------------- /src/chapter07/book-info-schema-example.js: -------------------------------------------------------------------------------- 1 | var bookInfo = { 2 | "publishers": [ 3 | "DC Comics" 4 | ], 5 | "number_of_pages": 334, 6 | "weight": "1.4 pounds", 7 | "physical_format": "Paperback", 8 | "subjects": [ 9 | "Graphic Novels", 10 | "Comics & Graphic Novels", 11 | "Fiction", 12 | "Fantastic fiction" 13 | ], 14 | "isbn_13": [ 15 | "9780930289232" 16 | ], 17 | "title": "Watchmen", 18 | "isbn_10": [ 19 | "0930289234" 20 | ], 21 | "publish_date": "April 1, 1995", 22 | "physical_dimensions": "10.1 x 6.6 x 0.8 inches" 23 | }; 24 | 25 | validate(bookInfoSchema, bookInfo); 26 | // → true 27 | -------------------------------------------------------------------------------- /src/chapter07/book-info-schema.js: -------------------------------------------------------------------------------- 1 | var basicBookInfoSchema = { 2 | "type": "object", 3 | "required": ["title"], 4 | "properties": { 5 | "title": {"type": "string"}, 6 | "publishers": { 7 | "type": "array", 8 | "items": {"type": "string"} 9 | }, 10 | "number_of_pages": {"type": "integer"}, 11 | "weight": {"type": "string"}, 12 | "physical_format": {"type": "string"}, 13 | "subjects": { 14 | "type": "array", 15 | "items": {"type": "string"} 16 | }, 17 | "isbn_13": { 18 | "type": "array", 19 | "items": {"type": "string"} 20 | }, 21 | "isbn_10": { 22 | "type": "array", 23 | "items": {"type": "string"} 24 | }, 25 | "publish_date": {"type": "string"}, 26 | "physical_dimensions": {"type": "string"} 27 | } 28 | }; 29 | 30 | var mandatoryIsbn13 = { 31 | "type": "object", 32 | "required": ["isbn_13"] 33 | }; 34 | 35 | var mandatoryIsbn10 = { 36 | "type": "object", 37 | "required": ["isbn_10"] 38 | }; 39 | 40 | var bookInfoSchema = { 41 | "allOf": [ 42 | basicBookInfoSchema, 43 | { 44 | "anyOf": [mandatoryIsbn13, mandatoryIsbn10] 45 | } 46 | ] 47 | }; 48 | -------------------------------------------------------------------------------- /src/chapter07/books-example.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "title": "7 Habits of Highly Effective People", 4 | "available": true, 5 | "isbn": "978-0812981605", 6 | "dummy_property": 42 7 | }, 8 | { 9 | "title": "The Power of Habit", 10 | "available": false, 11 | "isbn": "978-1982137274", 12 | "dummy_property": 45 13 | } 14 | ] 15 | -------------------------------------------------------------------------------- /src/chapter07/books-schema-2.js: -------------------------------------------------------------------------------- 1 | var booksFromDBSchema = { 2 | "type": "array", 3 | "items": { 4 | "type": "object", 5 | "required": ["title", "isbn", "available"], 6 | "additionalProperties": false, 7 | "properties": { 8 | "title": {"type": "string"}, 9 | "available": {"type": "boolean"}, 10 | "isbn": {"type": "string"} 11 | } 12 | } 13 | }; 14 | -------------------------------------------------------------------------------- /src/chapter07/books-schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "array", 3 | "items": { 4 | "type": "object", 5 | "required": ["title", "isbn", "available"], 6 | "properties": { 7 | "title": {"type": "string"}, 8 | "available": {"type": "boolean"}, 9 | "isbn": {"type": "string"} 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/chapter07/global-validate.js: -------------------------------------------------------------------------------- 1 | var ajvHidden = new Ajv(); 2 | validate = function(schema, data) { 3 | return ajvHidden.validate(schema, data); 4 | } 5 | 6 | -------------------------------------------------------------------------------- /src/chapter07/invalid-search-books.js: -------------------------------------------------------------------------------- 1 | var invalidSearchBooksRequest = { 2 | "tilte": "habit", 3 | "fields": ["title", "weight", "number_of_pages"] 4 | }; 5 | 6 | 7 | validate(searchBooksRequestSchema, invalidSearchBooksRequest); 8 | // → false 9 | -------------------------------------------------------------------------------- /src/chapter07/json-schema-super-example.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "array", // <1> 3 | "items": { 4 | "type": "object", // <2> 5 | "properties": { // <3> 6 | "myNumber": {"type": "number"}, // <4> 7 | "myString": {"type": "string"}, // <5> 8 | "myEnum": {"enum": ["myVal", "yourVal"]}, // <6> 9 | "myBool": {"type": "boolean"} // <7> 10 | }, 11 | "required": ["myNumber", "myString"], // <8> 12 | "additionalProperties": false // <9> 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/chapter07/map-schema-skeleton.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "object", 3 | "properties": {...} 4 | } 5 | -------------------------------------------------------------------------------- /src/chapter07/search-books-error-readable.js: -------------------------------------------------------------------------------- 1 | ajv.errorsText(ajv.errors); 2 | // → "data must have required property 'title'" 3 | -------------------------------------------------------------------------------- /src/chapter07/search-books-errors.js: -------------------------------------------------------------------------------- 1 | var searchBooksRequestSchema = { 2 | "type": "object", 3 | "properties": { 4 | "title": {"type": "string"}, 5 | "fields": { 6 | "type": "array", 7 | "items": {"type": "string"} 8 | } 9 | }, 10 | "required": ["title", "fields"] 11 | }; 12 | 13 | var invalidSearchBooksRequest = { 14 | "myTitle": "habit", 15 | "fields": ["title", "weight", "number_of_pages"] 16 | }; 17 | 18 | var ajv = new Ajv(); // <1> 19 | 20 | ajv.validate(searchBooksRequestSchema, invalidSearchBooksRequest); 21 | 22 | ajv.errors // <2> 23 | -------------------------------------------------------------------------------- /src/chapter07/search-books-multiple-errors.js: -------------------------------------------------------------------------------- 1 | var searchBooksRequestSchema = { 2 | "type": "object", 3 | "properties": { 4 | "title": {"type": "string"}, 5 | "fields": { 6 | "type": "array", 7 | "items": {"type": "string"} 8 | } 9 | }, 10 | "required": ["title", "fields"] 11 | }; 12 | 13 | var invalidSearchBooksRequest = { // <1> 14 | "myTitle": "habit", 15 | "fields": [1, 2] 16 | }; 17 | 18 | var ajv = new Ajv({allErrors: true}); // <2> 19 | 20 | ajv.validate(searchBooksRequestSchema, 21 | invalidSearchBooksRequest); 22 | 23 | ajv.errorsText(ajv.errors); // <3> 24 | // → "data must have required property 'title', 25 | // → data/fields/0 must be string, 26 | // → data/fields/1 must be string" 27 | 28 | -------------------------------------------------------------------------------- /src/chapter07/search-books-request-schema.js: -------------------------------------------------------------------------------- 1 | var searchBooksRequestSchema = { 2 | "type": "object", 3 | "properties": { 4 | "title": {"type": "string"}, 5 | "fields": { 6 | "type": "array", 7 | "items": { 8 | "enum": [ 9 | "publishers", 10 | "number_of_pages", 11 | "weight", 12 | "physical_format", 13 | "subjects", 14 | "publish_date", 15 | "physical_dimensions" 16 | ] 17 | } 18 | } 19 | }, 20 | "required": ["title", "fields"] 21 | }; 22 | -------------------------------------------------------------------------------- /src/chapter07/search-books-response-schema-sep.js: -------------------------------------------------------------------------------- 1 | 2 | var bookInfoSchema = { 3 | "type": "object", 4 | "required": ["title", "available"], 5 | "properties": { 6 | "title": {"type": "string"}, 7 | "available": {"type": "boolean"}, 8 | "subtitle": {"type": "string"}, 9 | "number_of_pages": {"type": "integer"}, 10 | "subjects": { 11 | "type": "array", 12 | "items": {"type": "string"} 13 | }, 14 | "isbn": {"type": "string"}, 15 | "isbn_13": {"type": "string"} 16 | } 17 | }; 18 | 19 | var searchBooksResponseSchema = { 20 | "type": "array", 21 | "items": bookInfoSchema 22 | }; 23 | -------------------------------------------------------------------------------- /src/chapter07/search-books-response-schema.js: -------------------------------------------------------------------------------- 1 | var searchBooksResponseSchema = { 2 | "type": "array", 3 | "items": { 4 | "type": "object", 5 | "required": ["title", "available"], 6 | "properties": { 7 | "title": {"type": "string"}, 8 | "available": {"type": "boolean"}, 9 | "subtitle": {"type": "string"}, 10 | "number_of_pages": {"type": "integer"}, 11 | "subjects": { 12 | "type": "array", 13 | "items": {"type": "string"} 14 | }, 15 | "isbn": {"type": "string"}, 16 | "isbn_13": {"type": "string"} 17 | } 18 | } 19 | }; 20 | -------------------------------------------------------------------------------- /src/chapter07/search-books-validation-failures.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "instancePath": "", 4 | "schemaPath": "#/required", 5 | "keyword": "required", 6 | "params": { 7 | "missingProperty":"title" 8 | }, 9 | "message": "must have required property 'title'" 10 | } 11 | ] 12 | -------------------------------------------------------------------------------- /src/chapter07/search-request-schema-skeleton-2.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "object", 3 | "properties": { 4 | "title": {"type": "string"}, 5 | "fields": { 6 | "type": "array", 7 | "items": {"type": "string"} 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/chapter07/search-request-schema-skeleton-3.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "object", 3 | "properties": { 4 | "title": {"type": "string"}, 5 | "fields": { 6 | "type": "array", 7 | "items": { 8 | "enum": [ 9 | "publishers", 10 | "number_of_pages", 11 | "weight", 12 | "physical_format", 13 | "subjects", 14 | "publish_date", 15 | "physical_dimensions" 16 | ] 17 | } 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/chapter07/search-request-schema-skeleton.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "object", 3 | "properties": { 4 | "title": {"type": "string"}, 5 | "fields": {"type": "array"} 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/chapter07/search-request.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "habit", 3 | "fields": ["title", "weight", "number_of_pages"] 4 | } 5 | -------------------------------------------------------------------------------- /src/chapter07/search-response.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "title": "7 Habits of Highly Effective People", 4 | "available": true, 5 | "isbn": "978-0812981605", 6 | "subtitle": "Powerful Lessons in Personal Change", 7 | "number_of_pages": 432 8 | }, 9 | { 10 | "title": "The Power of Habit", 11 | "available": false, 12 | "isbn_13": "978-1982137274", 13 | "subtitle": "Why We Do What We Do in Life and Business", 14 | "subjects": [ 15 | "Social aspects", 16 | "Habit", 17 | "Change (Psychology)" 18 | ] 19 | } 20 | ] 21 | -------------------------------------------------------------------------------- /src/chapter07/super-example.json: -------------------------------------------------------------------------------- 1 | [ 2 | { // <1> 3 | "myNumber": 42, 4 | "myString": "Hello", 5 | "myEnum": "myVal", 6 | "myBool": true 7 | }, 8 | { // <2> 9 | "myNumber": 54, 10 | "myString": "Happy" 11 | } 12 | ] 13 | -------------------------------------------------------------------------------- /src/chapter07/valid-search-books.js: -------------------------------------------------------------------------------- 1 | var searchBooksRequestSchema = { 2 | "type": "object", 3 | "properties": { 4 | "title": {"type": "string"}, 5 | "fields": { 6 | "type": "array", 7 | "items": {"type": "string"} 8 | } 9 | }, 10 | "required": ["title", "fields"] 11 | }; 12 | 13 | var searchBooksRequest = { 14 | "title": "habit", 15 | "fields": ["title", "weight", "number_of_pages"] 16 | }; 17 | 18 | 19 | validate(searchBooksRequestSchema, searchBooksRequest); 20 | // → true 21 | -------------------------------------------------------------------------------- /src/chapter08/atom.java: -------------------------------------------------------------------------------- 1 | class Atom { 2 | private AtomicReference state; 3 | 4 | public Atom() {} 5 | 6 | ValueType get() { 7 | return this.state.get(); 8 | } 9 | 10 | void set(ValueType state) { 11 | this.state.set(state); 12 | } 13 | 14 | ValueType swap(UnaryOPerator f) { 15 | while(true) { 16 | ValueType stateSnapshot = this.state.get(); 17 | ValueType nextState = f(stateSnapshot); 18 | if (!this.state.compareAndSet(stateSnapshot, nextState)) { // this.state might have changed in another thread during execution of f 19 | continue; 20 | } 21 | } 22 | return nextState; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/chapter08/atom.js: -------------------------------------------------------------------------------- 1 | class Atom { 2 | state; 3 | 4 | constructor() {} 5 | 6 | get() { 7 | return this.state; 8 | } 9 | 10 | set(state) { 11 | this.state = state; 12 | } 13 | 14 | swap(f) { 15 | while(true) { 16 | var stateSnapshot = this.state; 17 | var nextState = f(stateSnapshot); 18 | if (!atomicCompareAndSet(this.state, stateSnapshot, nextState)) { // <1> 19 | continue; 20 | } 21 | return nextState; 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/chapter08/cache-with-atom.js: -------------------------------------------------------------------------------- 1 | var cache = new Atom(); 2 | cache.set({}); 3 | 4 | function dbAccessCached(query) { 5 | var resultFromCache = _.get(cache.get(), query); 6 | if (resultFromCache != nil) { 7 | return resultFromCache; 8 | } 9 | var result = dbAccess(query); 10 | cache.swap(function(oldCache) { 11 | return _.set(oldCache, query, result); 12 | }); 13 | return result; 14 | } 15 | -------------------------------------------------------------------------------- /src/chapter08/cache-with-lock.js: -------------------------------------------------------------------------------- 1 | var mutex = new Mutex(); 2 | var cache = {}; 3 | 4 | function dbAccessCached(query) { 5 | var resultFromCache = _.get(cache, query); 6 | if (resultFromCache != nil) { 7 | return resultFromCache; 8 | } 9 | var result = dbAccess(query); 10 | mutex.lock(); 11 | cache = _.set(cache, query, result); 12 | mutex.unlock(); 13 | return result; 14 | } 15 | 16 | -------------------------------------------------------------------------------- /src/chapter08/counter-with-atom.java: -------------------------------------------------------------------------------- 1 | Atom counter = new Atom(); 2 | 3 | counter.set(0); 4 | 5 | counter.swap(x -> x + 1); 6 | 7 | counter.get(); 8 | -------------------------------------------------------------------------------- /src/chapter08/counter-with-atom.js: -------------------------------------------------------------------------------- 1 | var counter = new Atom(); 2 | counter.set(0); 3 | 4 | function dbAccess() { 5 | counter.swap(function(x) { // <1> 6 | return x + 1; 7 | }); 8 | // access the database 9 | } 10 | 11 | function logCounter() { 12 | console.log('Number of database accesses: ' + counter.get()); 13 | } 14 | -------------------------------------------------------------------------------- /src/chapter08/counter-with-lock.js: -------------------------------------------------------------------------------- 1 | var mutex = new Mutex(); 2 | var counter = 0; 3 | 4 | 5 | function dbAccess() { 6 | mutex.lock(); 7 | counter = counter + 1; 8 | mutex.unlock(); 9 | // access the database 10 | } 11 | 12 | function logCounter() { 13 | mutex.lock(); 14 | console.log('Number of database accesses: ' + counter); 15 | mutex.unlock(); 16 | } 17 | -------------------------------------------------------------------------------- /src/chapter08/system-data-commit.js: -------------------------------------------------------------------------------- 1 | SystemData.commit = function(previous, next) { 2 | this.systemData.swap(function(current) { 3 | return SystemConsistency.reconcile(current, 4 | previous, 5 | next); 6 | }); 7 | }; 8 | -------------------------------------------------------------------------------- /src/chapter08/system-data-single-thread.js: -------------------------------------------------------------------------------- 1 | class SystemState { 2 | systemData; 3 | 4 | get() { 5 | return this.systemData; 6 | } 7 | 8 | set(_systemData) { 9 | this.systemData = _systemData; 10 | } 11 | 12 | commit(previous, next) { 13 | this.systemData = SystemConsistency.reconcile(this.systemData, 14 | previous, 15 | next); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/chapter08/system-data.js: -------------------------------------------------------------------------------- 1 | class SystemState { 2 | systemData; 3 | 4 | constructor() { 5 | this.systemData = new Atom(); 6 | } 7 | 8 | get() { 9 | return this.systemData.get(); 10 | } 11 | 12 | commit(prev, next) { 13 | this.systemData.set(next); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/chapter09/add-member.js: -------------------------------------------------------------------------------- 1 | UserManagement.addMember = function(userManagement, member) { 2 | var email = Immutable.get(member, "email"); 3 | var infoPath = ["membersByEmail", email]; 4 | if(Immutable.hasIn(userManagement, infoPath)) { 5 | throw "Member already exists."; 6 | } 7 | var nextUserManagement = Immutable.setIn(userManagement, 8 | infoPath, 9 | member); 10 | return nextUserManagement; 11 | }; 12 | -------------------------------------------------------------------------------- /src/chapter09/assoc.java: -------------------------------------------------------------------------------- 1 | var myMap = PersistentHashMap.of(Map.of("aa", 1, "bb", 2).entrySet()); // <1> 2 | 3 | var myNextMap = myMap.assoc("aa", 42); 4 | -------------------------------------------------------------------------------- /src/chapter09/data-diff.js: -------------------------------------------------------------------------------- 1 | function diffObjects(data1, data2) { 2 | var emptyObject = Immutable.isArray(data1) ? Immutable.fromJS([]) : Immutable.fromJS({}); 3 | if(data1 == data2) { 4 | return emptyObject; 5 | } 6 | var keys = Immutable.union(Immutable.keys(data1), Immutable.keys(data2)); 7 | return Immutable.reduce(keys, 8 | function (acc, k) { 9 | var res = diff(Immutable.get(data1, k), 10 | Immutable.get(data2, k)); 11 | if((Immutable.isObject(res) && Immutable.isEmpty(res)) || 12 | (res == "data-diff:no-diff")) { 13 | return acc; 14 | } 15 | return Immutable.set(acc, k, res); 16 | }, 17 | emptyObject); 18 | } 19 | 20 | function diff(data1, data2) { 21 | if(Immutable.isObject(data1) && Immutable.isObject(data2)) { 22 | return diffObjects(data1, data2); 23 | } 24 | if(data1 !== data2) { 25 | return data2; 26 | } 27 | return "data-diff:no-diff"; 28 | } 29 | -------------------------------------------------------------------------------- /src/chapter09/deep-freeze.js: -------------------------------------------------------------------------------- 1 | function deepFreeze(object) { 2 | // Retrieve the property names defined on object 3 | const propNames = Object.getOwnPropertyNames(object); 4 | 5 | // Freeze properties before freezing self 6 | 7 | for (const name of propNames) { 8 | const value = object[name]; 9 | 10 | if (value && typeof value === "object") { 11 | deepFreeze(value); 12 | } 13 | } 14 | 15 | return Object.freeze(object); 16 | } 17 | 18 | -------------------------------------------------------------------------------- /src/chapter09/foreach.java: -------------------------------------------------------------------------------- 1 | var myVec = PersistentVector.ofIter(List.of(10, 2, 3)); // <1> 2 | 3 | for (Integer i : myVec) { 4 | System.out.println(i); 5 | } 6 | -------------------------------------------------------------------------------- /src/chapter09/freeze-object.js: -------------------------------------------------------------------------------- 1 | var a = [1, 2, 3]; 2 | Object.freeze(a); 3 | 4 | var b = {foo: 1}; 5 | Object.freeze(b); 6 | -------------------------------------------------------------------------------- /src/chapter09/get-in.js: -------------------------------------------------------------------------------- 1 | Immutable.get(libraryData, "catalog"); 2 | Immutable.getIn(libraryData, ["catalog", "booksByIsbn", "978-1779501127", "title"]); 3 | // → "Watchmen" 4 | -------------------------------------------------------------------------------- /src/chapter09/immutable-lodash-diff.js: -------------------------------------------------------------------------------- 1 | Immutable.reduce = function(coll, reducer, initialReduction) { 2 | return coll.reduce(reducer, initialReduction); 3 | }; 4 | 5 | Immutable.isEmpty = function(coll) { 6 | return coll.isEmpty(); 7 | }; 8 | 9 | Immutable.keys = function(coll) { 10 | return coll.keySeq(); 11 | }; 12 | 13 | Immutable.isObject = function(coll) { 14 | return Immutable.Map.isMap(coll); 15 | }; 16 | 17 | Immutable.isArray = Immutable.isIndexed; 18 | 19 | Immutable.union = function() { 20 | return Immutable.Set.union(arguments); 21 | }; 22 | 23 | -------------------------------------------------------------------------------- /src/chapter09/immutable-lodash-json.js: -------------------------------------------------------------------------------- 1 | Immutable.parseJSON = function(jsonString) { 2 | return Immutable.fromJS(JSON.parse(jsonString)); 3 | }; 4 | 5 | -------------------------------------------------------------------------------- /src/chapter09/immutable-lodash-search.js: -------------------------------------------------------------------------------- 1 | Immutable.map = function(coll, f) { 2 | return coll.map(f); 3 | }; 4 | 5 | Immutable.filter = function(coll, f) { 6 | if(Immutable.isMap(coll)) { 7 | return coll.valueSeq().filter(f); 8 | } 9 | return coll.filter(f); 10 | }; 11 | 12 | Immutable.isEqual = Immutable.is; 13 | -------------------------------------------------------------------------------- /src/chapter09/immutable-stringify.js: -------------------------------------------------------------------------------- 1 | var bookInfo = Immutable.fromJS({ 2 | "isbn": "978-1779501127", 3 | "title": "Watchmen", 4 | "authorNames": ["Alan Moore", 5 | "Dave Gibbons"] 6 | }); 7 | 8 | JSON.stringify(bookInfo); 9 | // → {\"isbn\":\"978-1779501127\",\"title\":\"Watchmen\", 10 | // → \"authorNames\":[\"Alan Moore\",\"Dave Gibbons\"]} 11 | -------------------------------------------------------------------------------- /src/chapter09/library-data.js: -------------------------------------------------------------------------------- 1 | var libraryData = Immutable.fromJS({ 2 | "catalog": { 3 | "booksByIsbn": { 4 | "978-1779501127": { 5 | "isbn": "978-1779501127", 6 | "title": "Watchmen", 7 | "publicationYear": 1987, 8 | "authorIds": ["alan-moore", 9 | "dave-gibbons"] 10 | } 11 | }, 12 | "authorsById": { 13 | "alan-moore": { 14 | "name": "Alan Moore", 15 | "bookIsbns": ["978-1779501127"] 16 | }, 17 | "dave-gibbons": { 18 | "name": "Dave Gibbons", 19 | "bookIsbns": ["978-1779501127"] 20 | } 21 | } 22 | } 23 | }); 24 | -------------------------------------------------------------------------------- /src/chapter09/make-immutable-list.java: -------------------------------------------------------------------------------- 1 | var myList = new ArrayList(); 2 | myList.add(1); 3 | myList.add(2); 4 | myList.add(3); 5 | 6 | var myImmutableList = List.of(myList.toArray()); 7 | -------------------------------------------------------------------------------- /src/chapter09/make-immutable-map.java: -------------------------------------------------------------------------------- 1 | var myMap = new HashMap(); 2 | myMap.put("name", "Isaac"); 3 | myMap.put("age", 42); 4 | 5 | var myImmutableMap = Collections.unmodifiableMap(myMap); 6 | -------------------------------------------------------------------------------- /src/chapter09/replace.java: -------------------------------------------------------------------------------- 1 | var myVec = PersistentVector.ofIter(List.of(10, 2, 3)); 2 | 3 | var myNextVec = myVec.replace(0, 42); 4 | -------------------------------------------------------------------------------- /src/chapter09/search.js: -------------------------------------------------------------------------------- 1 | class Catalog { 2 | static authorNames(catalogData, authorIds) { 3 | return Immutable.map(authorIds, function(authorId) { 4 | return Immutable.getIn(catalogData, ["authorsById", authorId, "name"]); 5 | }); 6 | } 7 | 8 | static bookInfo(catalogData, book) { 9 | var bookInfo = Immutable.Map({ 10 | "title": Immutable.get(book, "title"), 11 | "isbn": Immutable.get(book, "isbn"), 12 | "authorNames": Catalog.authorNames(catalogData, Immutable.get(book, "authorIds")) 13 | }); 14 | return bookInfo; 15 | } 16 | 17 | static searchBooksByTitle(catalogData, query) { 18 | var allBooks = Immutable.get(catalogData, "booksByIsbn"); 19 | var queryLowerCased = query.toLowerCase(); 20 | var matchingBooks = Immutable.filter(allBooks, function(book) { 21 | return Immutable.get(book, "title"). 22 | toLowerCase(). 23 | includes(queryLowerCased); 24 | }); 25 | var bookInfos = Immutable.map(matchingBooks, function(book) { 26 | return Catalog.bookInfo(catalogData, book); 27 | }); 28 | return bookInfos; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/chapter09/set-in.js: -------------------------------------------------------------------------------- 1 | Immutable.setIn(libraryData, 2 | ["catalog", "booksByIsbn", 3 | "978-1779501127", "publicationYear"], 4 | 1988); 5 | -------------------------------------------------------------------------------- /src/chapter09/stream.java: -------------------------------------------------------------------------------- 1 | var myVec = PersistentVector.ofIter(List.of(10, 2, 3)); 2 | 3 | vec1.stream().sorted().map(x -> x + 1); 4 | -------------------------------------------------------------------------------- /src/chapter09/test-add-member.js: -------------------------------------------------------------------------------- 1 | var jessie = Immutable.fromJS({ 2 | "email": "jessie@gmail.com", 3 | "password": "my-secret" 4 | }); 5 | 6 | var franck = Immutable.fromJS({ 7 | "email": "franck@gmail.com", 8 | "password": "my-top-secret" 9 | }); 10 | 11 | var userManagementStateBefore = Immutable.fromJS({ 12 | "membersByEmail": { 13 | "franck@gmail.com": { 14 | "email": "franck@gmail.com", 15 | "password": "my-top-secret" 16 | } 17 | } 18 | }); 19 | 20 | var expectedUserManagementStateAfter = Immutable.fromJS({ 21 | "membersByEmail": { 22 | "jessie@gmail.com": { 23 | "email": "jessie@gmail.com", 24 | "password": "my-secret" 25 | }, 26 | "franck@gmail.com": { 27 | "email": "franck@gmail.com", 28 | "password": "my-top-secret" 29 | } 30 | } 31 | }); 32 | 33 | var result = UserManagement.addMember(userManagementStateBefore, jessie); 34 | Immutable.isEqual(result, expectedUserManagementStateAfter); 35 | // → true 36 | -------------------------------------------------------------------------------- /src/chapter09/test-data-diff.js: -------------------------------------------------------------------------------- 1 | var data1 = Immutable.fromJS({ 2 | g: { 3 | c: 3 4 | }, 5 | x: 2, 6 | y: { 7 | z: 1 8 | }, 9 | w: [5] 10 | }); 11 | 12 | var data2 = Immutable.fromJS({ 13 | g: { 14 | c:3 15 | }, 16 | x: 2, 17 | y: { 18 | z: 2 19 | }, 20 | w: [4] 21 | }); 22 | 23 | Immutable.isEqual(diff(data1, data2), 24 | Immutable.fromJS({ 25 | "w": [ 26 | 4 27 | ], 28 | "y": { 29 | "z": 2 30 | } 31 | })); 32 | -------------------------------------------------------------------------------- /src/chapter09/test-search.js: -------------------------------------------------------------------------------- 1 | var catalogData = Immutable.fromJS({ 2 | "booksByIsbn": { 3 | "978-1779501127": { 4 | "isbn": "978-1779501127", 5 | "title": "Watchmen", 6 | "publicationYear": 1987, 7 | "authorIds": ["alan-moore", 8 | "dave-gibbons"] 9 | } 10 | }, 11 | "authorsById": { 12 | "alan-moore": { 13 | "name": "Alan Moore", 14 | "bookIsbns": ["978-1779501127"] 15 | }, 16 | "dave-gibbons": { 17 | "name": "Dave Gibbons", 18 | "bookIsbns": ["978-1779501127"] 19 | } 20 | } 21 | }); 22 | 23 | var bookInfo = Immutable.fromJS({ 24 | "isbn": "978-1779501127", 25 | "title": "Watchmen", 26 | "authorNames": ["Alan Moore", 27 | "Dave Gibbons"] 28 | }); 29 | 30 | Immutable.isEqual( 31 | Catalog.searchBooksByTitle(catalogData, "Watchmen"), 32 | Immutable.fromJS([bookInfo])); 33 | // → true 34 | 35 | Immutable.isEqual( 36 | Catalog.searchBooksByTitle(catalogData, "Batman"), 37 | Immutable.fromJS([])); 38 | // → true 39 | -------------------------------------------------------------------------------- /src/chapter10/add-member-0.js: -------------------------------------------------------------------------------- 1 | var addMemberQuery = "INSERT INTO members (email, password) VALUES ($1, $2)"; 2 | dbClient.query(addMemberQuery, 3 | [_.get(member, "email"), //<1> 4 | _.get(member, "encryptedPassword")]); 5 | -------------------------------------------------------------------------------- /src/chapter10/add-member.js: -------------------------------------------------------------------------------- 1 | class CatalogDB { 2 | static addMember(member) { 3 | var addMemberQuery = `INSERT 4 | INTO members 5 | (email, encrypted_password) 6 | VALUES ($1, $2)`; 7 | dbClient.query(addMemberQuery, 8 | _.at(member, ["email", 9 | "encryptedPassword"])); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/chapter10/add-member.sql: -------------------------------------------------------------------------------- 1 | INSERT 2 | INTO members 3 | (email, encrypted_password) 4 | VALUES ($1, $2) 5 | -------------------------------------------------------------------------------- /src/chapter10/aggregate-field.js: -------------------------------------------------------------------------------- 1 | var rows7Habits = [ 2 | { 3 | "author_name": "Sean Covey", 4 | "isbn": "978-1982137274", 5 | "title": "7 Habits of Highly Effective People" 6 | }, 7 | { 8 | "author_name": "Stephen Covey", 9 | "isbn": "978-1982137274", 10 | "title": "7 Habits of Highly Effective People" 11 | } 12 | ]; 13 | 14 | var authorNames = _.map(rows7Habits, "author_name"); // <1> 15 | var firstRow = _.nth(rows7Habits, 0); 16 | var bookInfoWithAuthorNames = _.set(firstRow, "authorNames", authorNames); 17 | _.omit(bookInfoWithAuthorNames, "author_name"); // <2> 18 | -------------------------------------------------------------------------------- /src/chapter10/aggregate-one-field.js: -------------------------------------------------------------------------------- 1 | function aggregateField(rows, fieldName, aggregateFieldName) { 2 | var aggregatedValues = _.map(rows, fieldName); 3 | var firstRow = _.nth(rows, 0); 4 | var firstRowWithAggregatedValues = _.set(firstRow, 5 | aggregateFieldName, 6 | aggregatedValues); 7 | return _.omit(firstRowWithAggregatedValues, fieldName); 8 | } 9 | -------------------------------------------------------------------------------- /src/chapter10/aggregate-rows.js: -------------------------------------------------------------------------------- 1 | var rowsByIsbn = _.groupBy(sqlRows, "isbn"); 2 | var groupedRows = _.values(rowsByIsbn); 3 | 4 | _.map(rowsByIsbn, function(groupedRows) { 5 | return aggregateField(groupedRows, "author_name", "authorNames"); 6 | }) 7 | -------------------------------------------------------------------------------- /src/chapter10/at.js: -------------------------------------------------------------------------------- 1 | var member = { 2 | "email": "samantha@gmail.com", 3 | "encryptedPassword": "c2VjcmV0", 4 | "isBlocked": false 5 | }; 6 | 7 | _.at(member, 8 | ["email", "encryptedPassword"]); 9 | // → ["samantha@gmail.com", "c2VjcmV0"] 10 | -------------------------------------------------------------------------------- /src/chapter10/db-search-schema.js: -------------------------------------------------------------------------------- 1 | var dbSearchResultSchema = { 2 | "type": "array", 3 | "items": { 4 | "type": "object", 5 | "required": ["title", "isbn", "publication_year"], 6 | "properties": { 7 | "title": {"type": "string"}, 8 | "isbn": {"type": "string"}, 9 | "publication_year": {"type": "integer"} 10 | } 11 | } 12 | }; 13 | -------------------------------------------------------------------------------- /src/chapter10/group-by-isbn.js: -------------------------------------------------------------------------------- 1 | var sqlRows = [ 2 | { 3 | "title": "7 Habits of Highly Effective People", 4 | "isbn": "978-1982137274", 5 | "author_name": "Sean Covey" 6 | }, 7 | { 8 | "title": "7 Habits of Highly Effective People", 9 | "isbn": "978-1982137274", 10 | "author_name": "Stephen Covey" 11 | }, 12 | { 13 | "title": "The Power of Habit", 14 | "isbn": "978-0812981605", 15 | "author_name": "Charles Duhigg" 16 | } 17 | ]; 18 | 19 | _.groupBy(sqlRows, "isbn"); 20 | -------------------------------------------------------------------------------- /src/chapter10/jdbc.java: -------------------------------------------------------------------------------- 1 | List> convertJDBCResultSetToListOfMaps(ResultSet rs) { 2 | List> listOfMaps = new ArrayList>(); 3 | ResultSetMetaData meta = rs.getMetaData(); 4 | while (rs.next()) { 5 | Map map = new HashMap(); 6 | for (int i = 1; i <= meta.getColumnCount(); i++) { 7 | String key = meta.getColumnLabel(i); 8 | Object value = rs.getObject(i); 9 | map.put(key, value); 10 | } 11 | listOfMaps.add(map); 12 | } 13 | return listOfMaps; 14 | } 15 | -------------------------------------------------------------------------------- /src/chapter10/json-schema-cheatsheet.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "array", 3 | "items": { 4 | "type": "object", 5 | "properties": { 6 | "myNumber": {"type": "number"}, 7 | "myString": {"type": "string"}, 8 | "myEnum": {"enum": ["myVal", "yourVal"]}, 9 | "myBool": {"type": "boolean"} 10 | }, 11 | "required": ["myNumber", "myString"], 12 | "additionalProperties": false 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/chapter10/rename-fields.js: -------------------------------------------------------------------------------- 1 | renameResultKeys(bookResults, { 2 | "title": "bookTitle", 3 | "publication_year": "publicationYear" 4 | }); 5 | -------------------------------------------------------------------------------- /src/chapter10/rename-result-keys-test.js: -------------------------------------------------------------------------------- 1 | var listOfMaps = [ 2 | { 3 | "title": "7 Habits of Highly Effective People", 4 | "isbn": "978-1982137274", 5 | "publication_year": 1989 6 | }, 7 | { 8 | "title": "The Power of Habit", 9 | "isbn": "978-0812981605", 10 | "publication_year": 2012 11 | } 12 | ]; 13 | 14 | var expectedResults = [ 15 | { 16 | "bookTitle": "7 Habits of Highly Effective People", 17 | "isbn": "978-1982137274", 18 | "publicationYear": 1989 19 | }, 20 | { 21 | "bookTitle": "The Power of Habit", 22 | "isbn": "978-0812981605", 23 | "publicationYear": 2012 24 | } 25 | ]; 26 | 27 | 28 | var results = renameResultKeys(listOfMaps, 29 | {"title": "bookTitle", 30 | "publication_year": "publicationYear"}); 31 | 32 | _.isEqual(expectedResults, results); 33 | 34 | -------------------------------------------------------------------------------- /src/chapter10/rename-result-keys.js: -------------------------------------------------------------------------------- 1 | function renameKeys(map, keyMap) { 2 | return _.reduce(keyMap, 3 | function(res, newKey, oldKey) { 4 | var value = _.get(map, oldKey); 5 | var resWithNewKey = _.set(res, newKey, value); 6 | var resWithoutOldKey = _.omit(resWithNewKey, oldKey); 7 | return resWithoutOldKey; 8 | }, 9 | map); 10 | } 11 | 12 | function renameResultKeys(results, keyMap) { 13 | return _.map(results, function(result) { 14 | return renameKeys(result, keyMap); 15 | }); 16 | } 17 | -------------------------------------------------------------------------------- /src/chapter10/rename-specific-keys.js: -------------------------------------------------------------------------------- 1 | function renameBookInfoKeys(bookInfo) { 2 | return { 3 | "bookTitle": _.get(bookInfo, "title"), 4 | "isbn": _.get(bookInfo, "isbn"), 5 | "publicationYear": _.get(bookInfo, "publication_year") 6 | }; 7 | } 8 | 9 | var bookResults = [ 10 | { 11 | "title": "7 Habits of Highly Effective People", 12 | "isbn": "978-1982137274", 13 | "publication_year": 1989 14 | }, 15 | { 16 | "title": "The Power of Habit", 17 | "isbn": "978-0812981605", 18 | "publication_year": 2012 19 | } 20 | ]; 21 | 22 | _.map(bookResults, renameBookInfoKeys); 23 | -------------------------------------------------------------------------------- /src/chapter10/rename-user-info.js: -------------------------------------------------------------------------------- 1 | var listOfMaps = [ 2 | { 3 | "email": "jennie@gmail.com", 4 | "encryptedPassword": "secret-pass" 5 | }, 6 | { 7 | "email": "franck@hotmail.com", 8 | "encryptedPassword": "my-secret" 9 | } 10 | ]; 11 | 12 | 13 | renameResultKeys(listOfMaps, 14 | {"email": "userEmail"}); 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/chapter10/rows-by-isbn.json: -------------------------------------------------------------------------------- 1 | { 2 | "978-0812981605": [ 3 | { 4 | "author_name": "Charles Duhigg", 5 | "isbn": "978-0812981605", 6 | "title": "The Power of Habit" 7 | } 8 | ], 9 | "978-1982137274": [ 10 | { 11 | "author_name": "Sean Covey", 12 | "isbn": "978-1982137274", 13 | "title": "7 Habits of Highly Effective People" 14 | }, 15 | { 16 | "author_name": "Stephen Covey", 17 | "isbn": "978-1982137274", 18 | "title": "7 Habits of Highly Effective People" 19 | } 20 | ] 21 | } 22 | -------------------------------------------------------------------------------- /src/chapter10/search-join.sql: -------------------------------------------------------------------------------- 1 | SELECT 2 | title, 3 | isbn, 4 | authors.name AS author_name 5 | FROM 6 | books 7 | INNER JOIN 8 | book_authors 9 | ON books.isbn = book_authors.book_isbn 10 | INNER JOIN 11 | authors 12 | ON book_authors.author_id = authors.id 13 | WHERE books.title LIKE '%habit%'; 14 | 15 | -------------------------------------------------------------------------------- /src/chapter10/search-results-extended.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "isbn": "978-1982137274", 4 | "title": "7 Habits of Highly Effective People", 5 | "authorNames": [ 6 | "Sean Covey", 7 | "Stephen Covey" 8 | ] 9 | }, 10 | { 11 | "isbn": "978-0812981605", 12 | "title": "The Power of Habit", 13 | "authorNames": ["Charles Duhigg"] 14 | } 15 | ] 16 | -------------------------------------------------------------------------------- /src/chapter10/search-simple-rename.sql: -------------------------------------------------------------------------------- 1 | SELECT 2 | title AS bookTitle, 3 | isbn, 4 | publication_year AS publicationYear 5 | FROM 6 | books 7 | WHERE title LIKE '%habit%'; 8 | 9 | -------------------------------------------------------------------------------- /src/chapter10/search-simple.js: -------------------------------------------------------------------------------- 1 | var dbClient; //<1> 2 | var ajv = new Ajv({allErrors: true}); // <2> 3 | 4 | var title = "habit"; 5 | var matchingBooksQuery = `SELECT title, isbn 6 | FROM books 7 | WHERE title LIKE '%$1%'`;// <3> 8 | var books = dbClient.query(matchingBooksQuery, [title]); // <4> 9 | if(!ajv.validate(dbSearchResultSchema, books)) { 10 | var errors = ajv.errorsText(ajv.errors); 11 | throw "Internal error: Unexpected result from the database: " + errors; 12 | } 13 | 14 | JSON.stringify(books); 15 | -------------------------------------------------------------------------------- /src/chapter10/search-simple.sql: -------------------------------------------------------------------------------- 1 | SELECT 2 | title, 3 | isbn, 4 | publication_year 5 | FROM 6 | books 7 | WHERE title LIKE '%habit%'; 8 | 9 | -------------------------------------------------------------------------------- /src/chapter10/sql-results.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "title": "7 Habits of Highly Effective People", 4 | "isbn": "978-1982137274", 5 | "publication_year": "Sean Covey" 6 | }, 7 | { 8 | "title": "7 Habits of Highly Effective People", 9 | "isbn": "978-1982137274", 10 | "author_name": "Stephen Covey" 11 | }, 12 | { 13 | "title": "The Power of Habit", 14 | "isbn": "978-0812981605", 15 | "author_name": "Charles Duhigg" 16 | } 17 | ] 18 | -------------------------------------------------------------------------------- /src/chapter10/sql-simple-results.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "title": "7 Habits of Highly Effective People", 4 | "isbn": "978-1982137274", 5 | "publication_year": 1989 6 | }, 7 | { 8 | "title": "The Power of Habit", 9 | "isbn": "978-0812981605", 10 | "publication_year": 2012 11 | } 12 | ] 13 | -------------------------------------------------------------------------------- /src/chapter10/test-aggregate-one-field.js: -------------------------------------------------------------------------------- 1 | var expectedResults = { 2 | "isbn": "978-1982137274", 3 | "title": "7 Habits of Highly Effective People", 4 | "authorNames": [ 5 | "Sean Covey", 6 | "Stephen Covey" 7 | ] 8 | }; 9 | 10 | _.isEqual(expectedResults, 11 | aggregateField(rows7Habits, 12 | "author_name", 13 | "authorNames")); 14 | -------------------------------------------------------------------------------- /src/chapter11/catalog-enrich.js: -------------------------------------------------------------------------------- 1 | class Catalog { 2 | static enrichedSearchBooksByTitle(searchPayload) { 3 | if(!ajv.validate(searchBooksRequestSchema, searchPayload)) { 4 | var errors = ajv.errorsText(ajv.errors); 5 | throw "Invalid request:" + errors; 6 | } 7 | var title = _.get(searchPayload, "title"); 8 | var fields = _.get(searchPayload, "fields"); 9 | 10 | var dbBookInfos = CatalogDataSource.matchingBooks(title); 11 | var isbns = _.map(dbBookInfos, "isbn"); 12 | 13 | var openLibBookInfos = OpenLibraryDataSource.multipleBookInfo(isbns, fields); 14 | 15 | var res = joinArrays(dbBookInfos, openLibBookInfos); 16 | if(!ajv.validate(searchBooksResponseSchema, request)) { 17 | var errors = ajv.errorsText(ajv.errors); 18 | throw "Invalid response:" + errors; 19 | } 20 | 21 | return res; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/chapter11/enrich-book-info-db.js: -------------------------------------------------------------------------------- 1 | var dbClient; 2 | var dbSearchResultSchema = { 3 | "type": "array", 4 | "items": { 5 | "type": "object", 6 | "required": ["isbn", "available"], 7 | "properties": { 8 | "isbn": {"type": "string"}, 9 | "available": {"type": "boolean"} 10 | } 11 | } 12 | }; 13 | 14 | class CatalogDB { 15 | static matchingBooks(title) { 16 | var matchingBooksQuery = ` 17 | SELECT isbn, available 18 | FROM books 19 | WHERE title = like '%$1%'; 20 | `; 21 | var books = dbClient.query(catalogDB, matchingBooksQuery, [title]); 22 | if(!ajv.validate(dbSearchResultSchema, books)) { 23 | var errors = ajv.errorsText(ajv.errors); 24 | throw "Internal error: Unexpected result from the database: " + errors; 25 | } 26 | 27 | return books; 28 | } 29 | } 30 | 31 | -------------------------------------------------------------------------------- /src/chapter11/enrich-book-info.js: -------------------------------------------------------------------------------- 1 | class Catalog { 2 | static enrichedSearchBooksByTitle(request) { 3 | if(!ajv.validate(searchBooksRequestSchema, request)) { 4 | var errors = ajv.errorsText(ajv.errors); 5 | throw "Invalid request:" + errors; 6 | } 7 | 8 | var title = _.get(request, "title"); 9 | var fields = _.get(request, "fields"); 10 | 11 | var dbBookInfos = CatalogDataSource.matchingBooks(title); 12 | var isbns = _.map(dbBookInfos, "isbn"); 13 | 14 | var openLibBookInfos = OpenLibraryDataSource.multipleBookInfo(isbns, fields); 15 | 16 | var response = joinArrays(dbBookInfos, openLibBookInfos); 17 | if(!ajv.validate(searchBooksResponseSchema, request)) { 18 | var errors = ajv.errorsText(ajv.errors); 19 | throw "Invalid response:" + errors; 20 | } 21 | return response; 22 | } 23 | } 24 | 25 | class Library { 26 | static searchBooksByTitle(payloadBody) { 27 | var payloadData = JSON.parse(payloadBody); 28 | var results = Catalog.searchBooksByTitle(payloadData); 29 | return JSON.stringify(results); 30 | } 31 | } 32 | 33 | -------------------------------------------------------------------------------- /src/chapter11/fetch-and-log.js: -------------------------------------------------------------------------------- 1 | function fetchAndLog(url) { 2 | fetch(url) 3 | .then(function(response) { 4 | // The response is a Response instance. 5 | // You parse the data into a useable format using `.json()` 6 | return response.json(); 7 | }).then(function(data) { 8 | // `data` is the parsed version of the JSON returned from the above endpoint. 9 | console.log(); // Insert a new line for clarity sake 10 | console.log(data); 11 | }); 12 | } 13 | -------------------------------------------------------------------------------- /src/chapter11/fetch-open-lib.js: -------------------------------------------------------------------------------- 1 | var ajv = new Ajv({allErrors: true}); 2 | class OpenLibraryDataSource { 3 | static rawBookInfo(isbn) { 4 | var url = `https://openlibrary.org/isbn/${isbn}.json`; 5 | var jsonString = fetchResponseBody(url); // <1> 6 | return JSON.parse(jsonString); 7 | } 8 | 9 | static bookInfo(isbn, requestedFields) { 10 | var relevantFields = ["title", "full_title", 11 | "subtitle", "publisher", 12 | "publish_date", "weight", 13 | "physical_dimensions", "genre", 14 | "subjects", "number_of_pages"]; 15 | var rawInfo = rawBookInfo(isbn); 16 | if(!ajv.validate(bookInfoSchema, rawInfo)) { 17 | var errors = ajv.errorsText(ajv.errors); 18 | throw "Internal error: Unexpected result from Open Books API: " + errors; 19 | } 20 | var relevantInfo = _.pick(_.pick(rawInfo, relevantFields), requestedFields); 21 | return _.set(relevantInfo, "isbn", isbn); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/chapter11/important-fields.txt: -------------------------------------------------------------------------------- 1 | - title 2 | - full_title 3 | - subtitle 4 | - publisher 5 | - publish_date 6 | - weight 7 | - physical_dimensions 8 | - number_of_pages 9 | - subjects 10 | - publishers 11 | - genre 12 | -------------------------------------------------------------------------------- /src/chapter11/join-arrays.js: -------------------------------------------------------------------------------- 1 | function joinArrays(a, b, keyA, keyB) { 2 | var mapA = _.keyBy(a, keyA); 3 | var mapB = _.keyBy(b, keyB); 4 | var mapsMerged = _.merge(mapA, mapB); 5 | return _.values(mapsMerged); 6 | } 7 | 8 | -------------------------------------------------------------------------------- /src/chapter11/key-by-result.json: -------------------------------------------------------------------------------- 1 | { 2 | "978-0812981605": { 3 | "available": false, 4 | "isbn": "978-0812981605", 5 | "title": "The Power of Habit" 6 | }, 7 | "978-1982137274": { 8 | "available": true, 9 | "isbn": "978-1982137274", 10 | "title": "7 Habits of Highly Effective People" 11 | } 12 | } 13 | 14 | -------------------------------------------------------------------------------- /src/chapter11/key-by.js: -------------------------------------------------------------------------------- 1 | var books = [ 2 | { 3 | "title": "7 Habits of Highly Effective People", 4 | "isbn": "978-1982137274", 5 | "available": true 6 | }, 7 | { 8 | "title": "The Power of Habit", 9 | "isbn": "978-0812981605", 10 | "available": false 11 | } 12 | ]; 13 | 14 | _.keyBy(books, "isbn"); 15 | 16 | -------------------------------------------------------------------------------- /src/chapter11/matching-books.js: -------------------------------------------------------------------------------- 1 | var dbSearchResultSchema = { 2 | "type": "array", 3 | "items": { 4 | "type": "object", 5 | "required": ["isbn", "available"], 6 | "properties": { 7 | "isbn": {"type": "string"}, 8 | "available": {"type": "boolean"} 9 | } 10 | } 11 | }; 12 | 13 | class CatalogDB { 14 | static matchingBooks(title) { 15 | var matchingBooksQuery = ` 16 | SELECT isbn, available 17 | FROM books 18 | WHERE title = like '%$1%'; 19 | `; 20 | var books = dbClient.query(catalogDB, matchingBooksQuery, [title]); 21 | if(!ajv.validate(dbSearchResultSchema, books)) { 22 | var errors = ajv.errorsText(ajv.errors); 23 | throw "Internal error: Unexpected result from the database: " + errors; 24 | } 25 | return books; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/chapter11/merge-data.js: -------------------------------------------------------------------------------- 1 | _.merge(dataFromDb, dataFromOpenLib); 2 | //{ 3 | // "available": true, 4 | // "full_title": "7 Habits of Highly Effective People :\ 5 | // Revised and Updated Powerful Lessons in Personal Change", 6 | // "isbn": "978-1982137274", 7 | // "number_of_pages": 432, 8 | // "publish_date": "2020", 9 | // "publishers": [ "Simon & Schuster, Incorporated"], 10 | // "subtitle": "Powerful Lessons in Personal Change", 11 | // "title": "7 Habits of Highly Effective People : Revised and Updated" 12 | //} 13 | -------------------------------------------------------------------------------- /src/chapter11/multiple-book-info.js: -------------------------------------------------------------------------------- 1 | class OpenLibraryDataSource { 2 | static rawBookInfo(isbn) { 3 | var url = `https://openlibrary.org/isbn/${isbn}.json`; 4 | var jsonString = fetchResponseBody(url); 5 | return JSON.parse(jsonString); 6 | } 7 | 8 | static bookInfo(isbn, requestedFields) { 9 | var relevantFields = ["title", "full_title", 10 | "subtitle", "publisher", 11 | "publish_date", "weight", 12 | "physical_dimensions", "genre", 13 | "subjects", "number_of_pages"]; 14 | var rawInfo = rawBookInfo(isbn); 15 | if(!ajv.validate(dbSearchResultSchema, bookInfoSchema)) { 16 | var errors = ajv.errorsText(ajv.errors); 17 | throw "Internal error: Unexpected result from Open Books API: " + errors; 18 | } 19 | var relevantInfo = _.pick(_.pick(rawInfo, relevantFields), requestedFields); 20 | return _.set(relevantInfo, "isbn", isbn); 21 | } 22 | 23 | static multipleBookInfo(isbns, fields) { 24 | return _.map(function(isbn) { 25 | return bookInfo(isbn, fields); 26 | }, isbns); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/chapter11/parse.java: -------------------------------------------------------------------------------- 1 | var jsonString = "{\"title\":\"habit\",\"fields\":[\"title\",\"weight\",\"number_of_pages\"]}"; 2 | gson.fromJson(jsonString, Map.class); 3 | -------------------------------------------------------------------------------- /src/chapter11/parse.js: -------------------------------------------------------------------------------- 1 | var jsonString = "{\"title\":\"habit\",\"fields\":[\"title\",\"weight\",\"number_of_pages\"]}"; 2 | JSON.parse(jsonString); 3 | -------------------------------------------------------------------------------- /src/chapter11/schema-response.js: -------------------------------------------------------------------------------- 1 | var searchBooksResponseSchema = { 2 | "type": "object", 3 | "required": ["title", "isbn", "available"], 4 | "properties": { 5 | "title": {"type": "string"}, 6 | "available": {"type": "boolean"}, 7 | "publishers": { 8 | "type": "array", 9 | "items": {"type": "string"} 10 | }, 11 | "number_of_pages": {"type": "integer"}, 12 | "weight": {"type": "string"}, 13 | "physical_format": {"type": "string"}, 14 | "subjects": { 15 | "type": "array", 16 | "items": {"type": "string"} 17 | }, 18 | "isbn": {"type": "string"}, 19 | "publish_date": {"type": "string"}, 20 | "physical_dimensions": {"type": "string"} 21 | } 22 | }; 23 | -------------------------------------------------------------------------------- /src/chapter11/search-json.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "habit", 3 | "fields": ["title", "weight", "number_of_pages"] 4 | } 5 | -------------------------------------------------------------------------------- /src/chapter11/search-request-schema.js: -------------------------------------------------------------------------------- 1 | var searchBooksRequestSchema = { 2 | "type": "object", 3 | "properties": { 4 | "title": {"type": "string"}, 5 | "fields": { 6 | "type": "array", 7 | "items": { 8 | "enum": [ 9 | "title", 10 | "full_title", 11 | "subtitle", 12 | "publisher", 13 | "publish_date", 14 | "weight", 15 | "physical_dimensions", 16 | "number_of_pages", 17 | "subjects", 18 | "publishers", 19 | "genre" 20 | ] 21 | } 22 | } 23 | }, 24 | "required": ["title", "fields"] 25 | }; 26 | -------------------------------------------------------------------------------- /src/chapter11/seven-habits-db.js: -------------------------------------------------------------------------------- 1 | var dataFromDb = { 2 | "available": true, 3 | "isbn": "978-1982137274" 4 | }; 5 | -------------------------------------------------------------------------------- /src/chapter11/seven-habits-open-api.js: -------------------------------------------------------------------------------- 1 | var dataFromOpenLib = { 2 | "title":"7 Habits of Highly Effective People : Revised and Updated", 3 | "subtitle":"Powerful Lessons in Personal Change", 4 | "number_of_pages":432, 5 | "full_title":"7 Habits of Highly Effective People : \ 6 | Revised and Updated Powerful Lessons in Personal Change", 7 | "publish_date":"2020", 8 | "publishers":["Simon & Schuster, Incorporated"] 9 | }; 10 | -------------------------------------------------------------------------------- /src/chapter11/seven-habits-open-api.json: -------------------------------------------------------------------------------- 1 | { 2 | "title":"7 Habits of Highly Effective People : Revised and Updated", 3 | "subtitle":"Powerful Lessons in Personal Change", 4 | "number_of_pages":432, 5 | "full_title":"7 Habits of Highly Effective People : Revised and Updated 6 | Powerful Lessons in Personal Change", 7 | "publish_date":"2020", 8 | "publishers":["Simon & Schuster, Incorporated"] 9 | } 10 | 11 | -------------------------------------------------------------------------------- /src/chapter12/author-schema.js: -------------------------------------------------------------------------------- 1 | var authorSchema = { 2 | "type": "object", 3 | "required": ["id", "name", "bookIsbns"], 4 | "properties": { 5 | "id": {"type": "string"}, 6 | "name": {"type": "string"}, 7 | "bookIsbns": { 8 | "type": "array", 9 | "items": {"type": "string"} 10 | } 11 | } 12 | }; 13 | -------------------------------------------------------------------------------- /src/chapter12/book-schema.js: -------------------------------------------------------------------------------- 1 | var bookItemSchema = { 2 | "type": "object", 3 | "properties":{ 4 | "id": {"type": "string"}, 5 | "libId": {"type": "string"}, 6 | "purchaseDate": {"type": "string"}, 7 | "isLent": {"type": "boolean"} 8 | }, 9 | "required": ["id", "libId", "purchaseDate", "isLent"] 10 | }; 11 | 12 | var bookSchema = { 13 | "type": "object", 14 | "required": ["title", "isbn", "authorIds", "bookItems"], 15 | "properties": { 16 | "title": {"type": "string"}, 17 | "publicationYear": {"type": "integer"}, 18 | "isbn": {"type": "string"}, 19 | "authorIds": { 20 | "type": "array", 21 | "items": {"type": "string"} 22 | }, 23 | "bookItems": { 24 | "type": "array", 25 | "items": bookItemSchema 26 | } 27 | } 28 | }; 29 | -------------------------------------------------------------------------------- /src/chapter12/catalog-generated.txt: -------------------------------------------------------------------------------- 1 | @startuml 2 | 3 | Entity1 *-- Entity2 4 | Entity1 *-- Entity4 5 | 6 | Entity2 *-- Entity3 7 | 8 | 9 | class Entity1 { 10 | + booksByIsbn: {Entity2} 11 | + authorsById: {Entity4} 12 | } 13 | 14 | class Entity2 { 15 | + title : String 16 | + publicationYear: Number 17 | + isbn: String 18 | + authorIds: [String] 19 | + bookItems: [Entity3] 20 | } 21 | 22 | class Entity3 { 23 | + id: String 24 | + libId: String 25 | + purchaseDate: String 26 | + isLent: Boolean 27 | } 28 | 29 | class Entity4 { 30 | + id: String 31 | + name: String 32 | + bookIsbns: [String] 33 | } 34 | 35 | @enduml 36 | -------------------------------------------------------------------------------- /src/chapter12/catalog-schema-part-2.js: -------------------------------------------------------------------------------- 1 | var bookSchema = { 2 | "type": "object", 3 | "required": ["title", "isbn", "authorIds", "bookItems"], 4 | "properties": { 5 | "title": {"type": "string"}, 6 | "publicationYear": publicationYearSchema, 7 | "isbn": isbnSchema, 8 | "publisher": {"type": "string"}, 9 | "authorIds": { 10 | "type": "array", 11 | "items": authorIdSchema 12 | }, 13 | "bookItems": bookItemSchema 14 | } 15 | }; 16 | 17 | var authorSchema = { 18 | "type": "object", 19 | "required": ["id", "name", "bookIsbns"], 20 | "properties": { 21 | "id": {"type": "string"}, 22 | "name": {"type": "string"}, 23 | "bookIsbns": { 24 | "items": isbnSchema 25 | } 26 | } 27 | }; 28 | 29 | var catalogSchema = { 30 | "type": "object", 31 | "properties": { 32 | "booksByIsbn": { 33 | "type": "object", 34 | "additionalProperties": bookSchema 35 | }, 36 | "authorsById": { 37 | "type": "object", 38 | "additionalProperties": authorSchema 39 | } 40 | }, 41 | "required": ["booksByIsbn", "authorsById"] 42 | }; 43 | -------------------------------------------------------------------------------- /src/chapter12/catalog-schema-simple.js: -------------------------------------------------------------------------------- 1 | var catalogSchema = { 2 | "type": "object", 3 | "properties": { 4 | "booksByIsbn": { 5 | "type": "object", 6 | "additionalProperties": bookSchema 7 | }, 8 | "authorsById": { 9 | "type": "object", 10 | "additionalProperties": authorSchema 11 | } 12 | }, 13 | "required": ["booksByIsbn", "authorsById"] 14 | }; 15 | -------------------------------------------------------------------------------- /src/chapter12/catalog-schema.js: -------------------------------------------------------------------------------- 1 | var isbnSchema = { 2 | "type": "string", 3 | "pattern": "^[0-9-]{10,20}$" 4 | }; 5 | 6 | var libIdSchema = { 7 | "type": "string", 8 | "pattern": "^[a-z0-9-]{3,20}$" 9 | }; 10 | 11 | var authorIdSchema ={ 12 | "type": "string", 13 | "pattern": "[a-z-]{2,50}" 14 | }; 15 | 16 | var bookItemSchema = { 17 | "type": "object", 18 | "additionalProperties": { 19 | "id": uuidSchema, 20 | "libId": libIdSchema, 21 | "purchaseDate": { 22 | "type": "string", 23 | "format": "date" 24 | }, 25 | "isLent": {"type": "boolean"} 26 | } 27 | }; 28 | -------------------------------------------------------------------------------- /src/chapter12/date-schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "string", 3 | "format": "date" 4 | } 5 | -------------------------------------------------------------------------------- /src/chapter12/json-schema-super-example-revised.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "array", // <1> 3 | "items": { 4 | "type": "object", // <2> 5 | "properties": { // <3> 6 | "myNumber": {"type": "number"}, // <4> 7 | "myString": {"type": "string"}, // <5> 8 | "myEnum": {"enum": ["myVal", "yourVal"]}, // <6> 9 | "myBool": {"type": "boolean"} // <7> 10 | "myAge": { // <8> 11 | "type": "integer", 12 | "minimum": 0, 13 | "maximum": 120 14 | }, 15 | "myBirthday": { // <9> 16 | "type": "string", 17 | "format": "date" 18 | }, 19 | "myLetters": { // <10> 20 | "type": "string", 21 | "pattern": "[a-zA-Z]*" 22 | } 23 | "myNumberMap": { // <11> 24 | "type": "object", 25 | "additionalProperties": {"type": "number"} 26 | }, 27 | "myTuple": { // <12> 28 | "type": "array", 29 | "prefixItems": [ 30 | { "type": "string" }, 31 | { "type": "number" } 32 | ] 33 | } 34 | }, 35 | "required": ["myNumber", "myString"], // <13> 36 | "additionalProperties": false // <14> 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/chapter12/number-map.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "object", 3 | "additionalProperties": {"type": "number"} 4 | } 5 | -------------------------------------------------------------------------------- /src/chapter12/publication-year-schema.js: -------------------------------------------------------------------------------- 1 | var publicationYearSchema = { 2 | "type": "integer", 3 | "minimum": 1900, 4 | "maximum": 2021 5 | }; 6 | -------------------------------------------------------------------------------- /src/chapter12/run-test.js: -------------------------------------------------------------------------------- 1 | searchBooksTest(); 2 | //→ false 3 | 4 | -------------------------------------------------------------------------------- /src/chapter12/search-again.js: -------------------------------------------------------------------------------- 1 | Catalog.searchBooksByTitle = function(catalogData, query) { 2 | if(dev()) { 3 | if(!ajv.validate(searchBooksArgsSchema, [catalogData, query])) { 4 | var errors = ajv.errorsText(ajv.errors); 5 | throw ("searchBooksByTitle called with invalid arguments: " + errors); 6 | } 7 | } 8 | 9 | var allBooks = _.get(catalogData, "booksByIsbn"); 10 | var matchingBooks = _.filter(allBooks, function(book) { 11 | return _.get(book, "title").includes(query); 12 | }); 13 | var bookInfos = _.map(matchingBooks, function(book) { 14 | return Catalog.bookInfo(catalogData, book); 15 | }); 16 | 17 | if(dev()) { 18 | if(!ajv.validate(searchBooksResponseSchema, bookInfos)) { 19 | var errors = ajv.errorsText(ajv.errors); 20 | throw ("searchBooksByTitle returned a value that doesn't conform to schema: " + errors); 21 | } 22 | } 23 | return bookInfos; 24 | }; 25 | -------------------------------------------------------------------------------- /src/chapter12/search-auto-test-1.js: -------------------------------------------------------------------------------- 1 | function searchBooksTest () { 2 | var catalogRandom = JSONSchemaFaker.generate(catalogSchema); 3 | var queryRandom = JSONSchemaFaker.generate({ "type": "string" }); 4 | try { 5 | var firstBook = _.values(_.get(catalogRandom, "booksByIsbn"))[0]; 6 | var query = _.get(firstBook, "title").substring(0,1); 7 | Catalog.searchBooksByTitle(catalogRandom, query); 8 | return true; 9 | } catch (error) { 10 | return false; 11 | } 12 | } 13 | 14 | -------------------------------------------------------------------------------- /src/chapter12/search-auto-test-2.js: -------------------------------------------------------------------------------- 1 | function searchBooksTest () { 2 | var catalogRandom = JSONSchemaFaker.generate(catalogSchema); 3 | var queryRandom = JSONSchemaFaker.generate({ "type": "string" }); 4 | try { 5 | var firstBook = _.values(_.get(catalogRandom, "booksByIsbn"))[0]; 6 | var query = _.get(firstBook, "title").substring(0,1); 7 | Catalog.searchBooksByTitle(catalogRandom, query); 8 | return true; 9 | } catch (error) { 10 | console.log(error); 11 | return false; 12 | } 13 | } 14 | 15 | -------------------------------------------------------------------------------- /src/chapter12/search-auto-test-catch.js: -------------------------------------------------------------------------------- 1 | function searchBooksTest () { 2 | var catalogRandom = JSONSchemaFaker.generate(catalogSchema); 3 | var queryRandom = JSONSchemaFaker.generate({ "type": "string" }); 4 | try { 5 | Catalog.searchBooksByTitle(catalogRandom, queryRandom); 6 | return true; 7 | } catch (error) { 8 | return false; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/chapter12/search-auto-test.js: -------------------------------------------------------------------------------- 1 | function searchBooksTest () { 2 | var catalogRandom = JSONSchemaFaker.generate(catalogSchema); 3 | var queryRandom = JSONSchemaFaker.generate({ "type": "string" }); 4 | Catalog.searchBooksByTitle(catalogRandom, queryRandom); 5 | } 6 | -------------------------------------------------------------------------------- /src/chapter12/search-books-arg-schema.js: -------------------------------------------------------------------------------- 1 | var searchBooksArgsSchema = { 2 | "type": "array", 3 | "prefixItems": [ 4 | catalogSchema, 5 | { "type": "string" }, 6 | ] 7 | }; 8 | -------------------------------------------------------------------------------- /src/chapter12/search-fixed.js: -------------------------------------------------------------------------------- 1 | Catalog.authorNames = function(catalogData, book) { 2 | var authorIds = _.get(book, "authorIds"); 3 | var names = _.map(authorIds, function(authorId) { 4 | return _.get(catalogData, ["authorsById", authorId, "name"], "Not available"); //<1> 5 | }); 6 | return names; 7 | }; 8 | -------------------------------------------------------------------------------- /src/chapter12/search-return-schema.js: -------------------------------------------------------------------------------- 1 | var searchBooksResponseSchema = { 2 | "type": "array", 3 | "items": { 4 | "type": "object", 5 | "required": ["title", "isbn", "authorNames"], 6 | "properties": { 7 | "title": {"type": "string"}, 8 | "isbn": {"type": "string"}, 9 | "authorNames": { 10 | "type": "array", 11 | "items": {"type": "string"} 12 | } 13 | } 14 | } 15 | }; 16 | -------------------------------------------------------------------------------- /src/chapter12/search-with-input-validation.js: -------------------------------------------------------------------------------- 1 | Catalog.searchBooksByTitle = function(catalogData, query) { 2 | if(dev()) { // <1> 3 | var args = [catalogData, query]; 4 | if(!ajv.validate(searchBooksArgsSchema, args)) { 5 | var errors = ajv.errorsText(ajv.errors); 6 | throw ("searchBooksByTitle called with invalid arguments: " + errors); 7 | } 8 | } 9 | 10 | var allBooks = _.get(catalogData, "booksByIsbn"); 11 | var matchingBooks = _.filter(allBooks, function(book) { 12 | return _.get(book, "title").includes(query); 13 | }); 14 | var bookInfos = _.map(matchingBooks, function(book) { 15 | return Catalog.bookInfo(catalogData, book); 16 | }); 17 | 18 | return bookInfos; 19 | }; 20 | -------------------------------------------------------------------------------- /src/chapter12/search-without-validation.js: -------------------------------------------------------------------------------- 1 | class Catalog { 2 | static authorNames(catalogData, book) { 3 | var authorIds = _.get(book, "authorIds"); 4 | var names = _.map(authorIds, function(authorId) { 5 | return _.get(catalogData, ["authorsById", authorId, "name"]); 6 | }); 7 | return names; 8 | } 9 | 10 | static bookInfo(catalogData, book) { 11 | var bookInfo = { 12 | "title": _.get(book, "title"), 13 | "isbn": _.get(book, "isbn"), 14 | "authorNames": Catalog.authorNames(catalogData, book) 15 | }; 16 | return bookInfo; 17 | } 18 | 19 | static searchBooksByTitle(catalogData, query) { 20 | var allBooks = _.get(catalogData, "booksByIsbn"); 21 | var matchingBooks = _.filter(allBooks, function(book) { 22 | return _.get(book, "title").includes(query); 23 | }); 24 | var bookInfos = _.map(matchingBooks, function(book) { 25 | return Catalog.bookInfo(catalogData, book); 26 | }); 27 | return bookInfos; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/chapter12/search.js: -------------------------------------------------------------------------------- 1 | Catalog.searchBooksByTitle = function(catalogData, query) { 2 | if(dev()) { 3 | if(!ajv.validate(searchBooksArgsSchema, [catalogData, query])) { 4 | var errors = ajv.errorsText(ajv.errors); 5 | throw ("searchBooksByTitle called with invalid arguments: " + errors); 6 | } 7 | } 8 | 9 | var allBooks = _.get(catalogData, "booksByIsbn"); 10 | var matchingBooks = _.filter(allBooks, function(book) { 11 | return _.get(book, "title").includes(query); 12 | }); 13 | var bookInfos = _.map(matchingBooks, function(book) { 14 | return Catalog.bookInfo(catalogData, book); 15 | }); 16 | 17 | if(dev()) { 18 | if(!ajv.validate(searchBooksResponseSchema, bookInfos)) { 19 | var errors = ajv.errorsText(ajv.errors); 20 | throw ("searchBooksByTitle returned a value that doesn't conform to schema: " + errors); 21 | } 22 | } 23 | return bookInfos; 24 | }; 25 | -------------------------------------------------------------------------------- /src/chapter12/super-example-revised.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "myNumber": 42, 4 | "myString": "I-love-you", 5 | "myEnum": "myVal", 6 | "myBool": true, 7 | "myTuple": ["Hello", 42] 8 | }, 9 | { 10 | "myNumber": 54, 11 | "myString": "Happy", 12 | "myAge": 42, 13 | "myBirthday": "1978-11-23", 14 | "myLetters": "Hello", 15 | "myNumberMap": { 16 | "banana": 23, 17 | "apple": 34 18 | } 19 | } 20 | ] 21 | -------------------------------------------------------------------------------- /src/chapter12/tuple.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "array", 3 | "prefixItems": [ 4 | { "type": "string" }, 5 | { "type": "number" } 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /src/chapter12/unit-test-output.js: -------------------------------------------------------------------------------- 1 | searchBooksTest(); 2 | // → searchBooksByTitle returned a value that doesn\'t conform to schema: 3 | // data[0].authorNames[0] should be string, 4 | // data[0].authorNames[1] should be string, 5 | // data[1].authorNames[0] should be string 6 | -------------------------------------------------------------------------------- /src/chapter12/unit-test-pass.js: -------------------------------------------------------------------------------- 1 | searchBooksTest(); 2 | // → true 3 | 4 | -------------------------------------------------------------------------------- /src/chapter12/uuid-random.js: -------------------------------------------------------------------------------- 1 | var uuidSchema = { 2 | "type": "string", 3 | "pattern": "[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}" 4 | }; 5 | 6 | JSONSchemaFaker.generate(uuidSchema); 7 | // → "7aA8CdF3-14DF-9EF5-1A19-47dacdB16Fa9" 8 | 9 | -------------------------------------------------------------------------------- /src/chapter12/uuid-regex.txt: -------------------------------------------------------------------------------- 1 | [0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12} 2 | -------------------------------------------------------------------------------- /src/chapter12/uuid-schema.js: -------------------------------------------------------------------------------- 1 | var uuidSchema = { 2 | "type": "string", 3 | "pattern": "[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}" 4 | }; 5 | 6 | -------------------------------------------------------------------------------- /src/chapter13/animal-dispatch.js: -------------------------------------------------------------------------------- 1 | function greetDispatch(animal) { // <1> 2 | if(dev()) { 3 | if(!ajv.validate(animalSchema, animal)) { // <2> 4 | var errors = ajv.errorsText(ajv.errors); 5 | throw ("greet called with invalid arguments: " + errors); 6 | } 7 | } 8 | 9 | return animal.type; // <3> 10 | } 11 | 12 | var greet = multi(greetDispatch); // <4> 13 | 14 | -------------------------------------------------------------------------------- /src/chapter13/animal-functions.js: -------------------------------------------------------------------------------- 1 | function greetDog(animal) { 2 | console.log("Woof Woof! My name is: " + animal.name); 3 | } 4 | 5 | function greetCat(animal) { 6 | console.log("Meow! I am: " + animal.name); 7 | } 8 | 9 | function greetCow(animal) { 10 | console.log("Moo! Call me " + animal.name); 11 | } 12 | 13 | function greet(animal) { 14 | if(dev()) { 15 | if(!ajv.validate(animalSchema, animal)) { 16 | var errors = ajv.errorsText(ajv.errors); 17 | throw ("greet called with invalid arguments: " + errors); 18 | } 19 | } 20 | switch (animal.type) { 21 | case "dog": 22 | greetDog(animal); 23 | break; 24 | case "cat": 25 | greetCat(animal); 26 | break; 27 | case "cow": 28 | greetCow(animal); 29 | break; 30 | }; 31 | } 32 | -------------------------------------------------------------------------------- /src/chapter13/animal-lang-dispatch.js: -------------------------------------------------------------------------------- 1 | var greetLangArgsSchema = { 2 | "type": "array", 3 | "prefixItems": [animalSchema, languageSchema] 4 | }; 5 | 6 | function greetLangDispatch(animal, language) { 7 | if(dev()) { 8 | if(!ajv.validate(greetLangArgsSchema, [animal, language])) { 9 | throw ("greetLang called with invalid arguments: " + ajv.errorsText(ajv.errors)); 10 | } 11 | } 12 | return [animal.type, language.type]; 13 | }; 14 | 15 | var greetLang = multi(greetLangDispatch); 16 | -------------------------------------------------------------------------------- /src/chapter13/animal-oop.java: -------------------------------------------------------------------------------- 1 | interface IAnimal { 2 | public void greet(); 3 | } 4 | 5 | class Dog implements IAnimal { 6 | private String name; 7 | public void greet() { 8 | System.out.println("Woof woof! My name is " + animal.name); 9 | } 10 | } 11 | 12 | class Cat implements IAnimal { 13 | private String name; 14 | public void greet() { 15 | System.out.println("Meow! I am " + animal.name); 16 | } 17 | } 18 | 19 | 20 | class Cow implements IAnimal { 21 | private String name; 22 | public void greet() { 23 | System.out.println("Moo! Call me " + animal.name); 24 | } 25 | } 26 | 27 | -------------------------------------------------------------------------------- /src/chapter13/animal-presentation.js: -------------------------------------------------------------------------------- 1 | var myDog = { 2 | "type": "dog", 3 | "name": "Fido" 4 | }; 5 | 6 | 7 | var myCat = { 8 | "type": "cat", 9 | "name": "Milo" 10 | }; 11 | 12 | 13 | var myCow = { 14 | "type": "cow", 15 | "name": "Clarabelle" 16 | }; 17 | -------------------------------------------------------------------------------- /src/chapter13/animal-switch-validate.js: -------------------------------------------------------------------------------- 1 | var animalSchema = { 2 | "type": "object", 3 | "properties": { 4 | "name": {"type": "string"}, 5 | "type": {"type": "string"} 6 | }, 7 | "required": ["name", "type"], 8 | }; 9 | 10 | function greet(animal) { 11 | if(dev()) { // <1> 12 | if(!ajv.validate(animalSchema, animal)) { 13 | var errors = ajv.errorsText(ajv.errors); 14 | throw ("greet called with invalid arguments: " + errors); 15 | } 16 | } 17 | switch (animal.type) { 18 | case "dog": 19 | console.log("Woof Woof! My name is: " + animal.name); 20 | break; 21 | case "cat": 22 | console.log("Meow! I am: " + animal.name); 23 | break; 24 | case "cow": 25 | console.log("Moo! Call me " + animal.name); 26 | break; 27 | }; 28 | } 29 | -------------------------------------------------------------------------------- /src/chapter13/animal-switch.js: -------------------------------------------------------------------------------- 1 | function greet(animal) { 2 | switch (animal.type) { 3 | case "dog": 4 | console.log("Woof Woof! My name is: " + animal.name); 5 | break; 6 | case "cat": 7 | console.log("Meow! I am: " + animal.name); 8 | break; 9 | case "cow": 10 | console.log("Moo! Call me " + animal.name); 11 | break; 12 | }; 13 | } 14 | -------------------------------------------------------------------------------- /src/chapter13/author-html.html: -------------------------------------------------------------------------------- 1 | Yehonathan Sharvit <1> 2 | Stephen Covey <2> 3 | Isaac Asimov <3> 4 | -------------------------------------------------------------------------------- /src/chapter13/author-info-dispatch.js: -------------------------------------------------------------------------------- 1 | Author.prolificityLevel = function(author) { 2 | var books = _.size(_.get(author, "bookIsbns")); 3 | if (books <= 10) { 4 | return "low"; 5 | }; 6 | if (books >= 51) { 7 | return "high"; 8 | } 9 | return "medium"; 10 | }; 11 | 12 | var authorNameArgsSchema = { 13 | "type": "array", 14 | "prefixItems": [ 15 | authorSchema, 16 | {"enum": ["markdown", "html"]} 17 | ] 18 | }; 19 | 20 | function authorNameDispatch(author, format) { 21 | if(dev()) { 22 | if(!ajv.validate(authorNameArgsSchema, [author, format])) { 23 | throw ("Author.myName called with invalid arguments: " + ajv.errorsText(ajv.errors)); 24 | } 25 | } 26 | 27 | return [Author.prolificityLevel(author), format]; 28 | }; 29 | 30 | Author.myName = multi(authorNameDispatch); 31 | 32 | -------------------------------------------------------------------------------- /src/chapter13/author-markdown.md: -------------------------------------------------------------------------------- 1 | *Yehonathan Sharvit* <1> 2 | **Stephen Covey** <2> 3 | ***Isaac Asimov*** <3> 4 | -------------------------------------------------------------------------------- /src/chapter13/author-name-html-methods.js: -------------------------------------------------------------------------------- 1 | function authorNameLowHtml(author, format) { 2 | return "" + _.get(author, "name") + ""; 3 | } 4 | 5 | Author.myName = method(["low", "html"], authorNameLowHtml)(Author.myName); 6 | 7 | function authorNameMediumHtml(author, format) { 8 | return "" + _.get(author, "name") + ""; 9 | } 10 | 11 | Author.myName = method(["medium", "html"], authorNameMediumHtml)(Author.myName); 12 | 13 | function authorNameHighHtml(author, format) { 14 | return "" + _.get(author, "name") + ""; 15 | } 16 | 17 | Author.myName = method(["high", "html"], authorNameHighHtml)(Author.myName); 18 | 19 | -------------------------------------------------------------------------------- /src/chapter13/author-name-markdown-methods.js: -------------------------------------------------------------------------------- 1 | function authorNameLowMarkdown(author, format) { 2 | return "*" + _.get(author, "name") + "*"; 3 | } 4 | 5 | Author.myName = method(["low", "markdown"], authorNameLowMarkdown)(Author.myName); 6 | 7 | function authorNameMediumMarkdown(author, format) { 8 | return "**" + _.get(author, "name") + "**"; 9 | } 10 | 11 | Author.myName = method(["medium", "markdown"], authorNameMediumMarkdown)(Author.myName); 12 | 13 | function authorNameHighMarkdown(author, format) { 14 | return "***" + _.get(author, "name") + "***"; 15 | } 16 | 17 | Author.myName = method(["high", "markdown"], authorNameHighMarkdown)(Author.myName); 18 | -------------------------------------------------------------------------------- /src/chapter13/author-name-test-markdown.js: -------------------------------------------------------------------------------- 1 | Author.myName(yehonathan, "markdown"); 2 | // → "*Yehonathan Sharvit*" 3 | 4 | -------------------------------------------------------------------------------- /src/chapter13/author-names-test.js: -------------------------------------------------------------------------------- 1 | var yehonathan = { 2 | "name": "Yehonathan Sharvit", 3 | "bookIsbns": ["9781617298578"] 4 | }; 5 | 6 | Author.myName(yehonathan, "html"); 7 | // → "Yehonathan Sharvit" 8 | -------------------------------------------------------------------------------- /src/chapter13/author-schema.js: -------------------------------------------------------------------------------- 1 | var authorSchema = { 2 | "type": "object", 3 | "required": ["name", "bookIsbns"], 4 | "properties": { 5 | "name": {"type": "string"}, 6 | "bookIsbns": { 7 | "type": "array", 8 | "items": {"type": "string"} 9 | } 10 | } 11 | }; 12 | -------------------------------------------------------------------------------- /src/chapter13/dys-greet-all.js: -------------------------------------------------------------------------------- 1 | dysGreet(myDog); 2 | dysGreet(myCow); 3 | dysGreet(myCat); 4 | //"Woof woof!" 5 | //"Moo! Call me Clarabelle" 6 | //"Meow!" 7 | -------------------------------------------------------------------------------- /src/chapter13/dys-greet-dispatch.js: -------------------------------------------------------------------------------- 1 | function dysGreetDispatch(animal) { 2 | if(dev()) { 3 | if(!ajv.validate(animalSchema, animal)) { 4 | var errors = ajv.errorsText(ajv.errors); 5 | throw ("dysGreet called with invalid arguments: " + errors); 6 | } 7 | } 8 | var hasLongName = animal.name.length > 5; 9 | 10 | return [animal.type, hasLongName]; 11 | }; 12 | 13 | var dysGreet = multi(dysGreetDispatch); 14 | 15 | -------------------------------------------------------------------------------- /src/chapter13/dys-greet-methods.js: -------------------------------------------------------------------------------- 1 | function dysGreetDogLong(animal) { 2 | console.log("Woof woof! My name is " + animal.name); 3 | } 4 | dysGreet = method(["dog", true], dysGreetDogLong)(dysGreet); 5 | 6 | function dysGreetDogShort(animal) { 7 | console.log("Woof woof!"); 8 | } 9 | dysGreet = method(["dog", false], dysGreetDogShort)(dysGreet); 10 | 11 | function dysGreetCatLong(animal) { 12 | console.log("Meow! I am " + animal.name); 13 | } 14 | dysGreet = method(["cat", true], dysGreetCatLong)(dysGreet); 15 | 16 | function dysGreetCatShort(animal) { 17 | console.log("Meow!"); 18 | } 19 | dysGreet = method(["cat", false], dysGreetCatShort)(dysGreet); 20 | 21 | function dysGreetCowLong(animal) { 22 | console.log("Moo! Call me " + animal.name); 23 | } 24 | dysGreet = method(["cow", true], dysGreetCowLong)(dysGreet); 25 | 26 | function dysGreetCowShort(animal) { 27 | console.log("Moo!"); 28 | } 29 | dysGreet = method(["cow", false], dysGreetCowShort)(dysGreet); 30 | 31 | -------------------------------------------------------------------------------- /src/chapter13/format-schema.js: -------------------------------------------------------------------------------- 1 | var textFormatSchema = { 2 | "name": {"type": "string"}, 3 | "type": {"enum": ["markdown", "html"]} 4 | }; 5 | -------------------------------------------------------------------------------- /src/chapter13/greet-cat.js: -------------------------------------------------------------------------------- 1 | function greetCat(animal) { 2 | console.log("Meow! I am " + animal.name); 3 | } 4 | 5 | greet = method("cat", greetCat)(greet); 6 | -------------------------------------------------------------------------------- /src/chapter13/greet-cow-call.js: -------------------------------------------------------------------------------- 1 | greet(myDog); 2 | // → "Woof woof! My name is Fido" 3 | 4 | greet(myCat); 5 | // → "Meow! I am Milo" 6 | 7 | greet(myCow); 8 | // → "Moo! Call me Clarabelle" 9 | -------------------------------------------------------------------------------- /src/chapter13/greet-cow.js: -------------------------------------------------------------------------------- 1 | function greetCow(animal) { 2 | console.log("Moo! Call me " + animal.name); 3 | } 4 | 5 | greet = method("cow", greetCow)(greet); 6 | 7 | -------------------------------------------------------------------------------- /src/chapter13/greet-default.js: -------------------------------------------------------------------------------- 1 | function greetDefault(animal) { 2 | console.log("My name is " + animal.name); 3 | } 4 | greet = method(greetDefault)(greet); 5 | 6 | -------------------------------------------------------------------------------- /src/chapter13/greet-dog-french.js: -------------------------------------------------------------------------------- 1 | greetLang(myDog, french); 2 | // → "Ouaf Ouaf! Je m\'appelle Fido et je parle Français" 3 | 4 | greetLang(myDog, english); 5 | // → "Woof woof! My name is Fido and I speak English" 6 | 7 | greetLang(myCat, french); 8 | // → "Miaou! Je m\'appelle Milo et je parle Français" 9 | 10 | greetLang(myCat, english); 11 | // → "Meow! I am Milo and I speak English" 12 | 13 | greetLang(myCow, french); 14 | // → "Meuh! Appelle moi Clarabelle et je parle Français" 15 | 16 | greetLang(myCow, english); 17 | // → "Moo! Call me Clarabelle and I speak English" 18 | -------------------------------------------------------------------------------- /src/chapter13/greet-dog.js: -------------------------------------------------------------------------------- 1 | function greetDog(animal) { // <1> 2 | console.log("Woof woof! My name is " + animal.name); 3 | } 4 | greet = method("dog", greetDog)(greet); // <2> 5 | -------------------------------------------------------------------------------- /src/chapter13/greet-horse.js: -------------------------------------------------------------------------------- 1 | var myHorse = { 2 | "type": "horse", 3 | "name": "Horace" 4 | }; 5 | greet(myHorse); 6 | // → "My name is Horace" 7 | -------------------------------------------------------------------------------- /src/chapter13/language-schema.js: -------------------------------------------------------------------------------- 1 | var languageSchema = { 2 | "type": "object", 3 | "properties": { 4 | "name": {"type": "string"}, 5 | "type": {"type": "string"} 6 | }, 7 | "required": ["name", "type"], 8 | }; 9 | 10 | -------------------------------------------------------------------------------- /src/chapter13/languages.js: -------------------------------------------------------------------------------- 1 | var french = { 2 | "type": "fr", 3 | "name": "Français" 4 | }; 5 | 6 | var english = { 7 | "type": "en", 8 | "name": "English" 9 | }; 10 | 11 | -------------------------------------------------------------------------------- /src/chapter14/array-of-arrays.js: -------------------------------------------------------------------------------- 1 | [ 2 | ["sean-covey", "stephen-covey"], 3 | ["alan-moore", "dave-gibbons"] 4 | ] 5 | 6 | -------------------------------------------------------------------------------- /src/chapter14/author-ids-0.js: -------------------------------------------------------------------------------- 1 | function authorIdsInBooks(books) { 2 | return _.map(books, "authorIds"); 3 | } 4 | -------------------------------------------------------------------------------- /src/chapter14/author-ids-1.js: -------------------------------------------------------------------------------- 1 | function authorIdsInBooks(books) { 2 | return _.flatten(_.map(books, "authorIds")); 3 | } 4 | 5 | -------------------------------------------------------------------------------- /src/chapter14/author-ids-2.js: -------------------------------------------------------------------------------- 1 | function authorIdsInBooks(books) { 2 | return flatMap(books, "authorIds"); 3 | } 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/chapter14/books-by-lib-unwind.js: -------------------------------------------------------------------------------- 1 | function booksByRack(books) { 2 | var bookItems = flatMap(books, function(book) { 3 | return unwind(book, "bookItems"); 4 | }); 5 | return _.groupBy(bookItems, "bookItems.libId") 6 | } 7 | -------------------------------------------------------------------------------- /src/chapter14/count-by-bool-field-test.js: -------------------------------------------------------------------------------- 1 | var input = [ 2 | {"a": true}, 3 | {"a": false}, 4 | {"a": true}, 5 | {"a": true} 6 | ]; 7 | 8 | var expectedRes = { 9 | "aTrue": 3, 10 | "aFalse": 1 11 | }; 12 | 13 | _.isEqual(countByBoolField(input, "a", "aTrue", "aFalse"), expectedRes); 14 | 15 | -------------------------------------------------------------------------------- /src/chapter14/count-by-bool-field.js: -------------------------------------------------------------------------------- 1 | function inc (n) { 2 | return n + 1; 3 | } 4 | 5 | function countByBoolField(coll, field, keyTrue, keyFalse) { 6 | return _.reduce(coll, function(res, item) { 7 | if (_.get(item, field)) { 8 | return update(res, keyTrue, inc); 9 | } 10 | return update(res, keyFalse, inc); 11 | }, {[keyTrue]: 0, // <1> 12 | [keyFalse]: 0}); 13 | } 14 | 15 | -------------------------------------------------------------------------------- /src/chapter14/flat-map.js: -------------------------------------------------------------------------------- 1 | function flatMap(coll, f) { 2 | return _.flatten(_.map(coll,f)); 3 | } 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/chapter14/lending-ratio-0.js: -------------------------------------------------------------------------------- 1 | function lendingRatio(books) { 2 | var bookItems = flatMap(books, "bookItems"); 3 | var lent = 0; 4 | var notLent = 0; 5 | _.forEach(bookItems, function(item) { 6 | if(_.get(item, "isLent")) { 7 | lent = lent + 1; 8 | } else { 9 | notLent = notLent + 1; 10 | } 11 | }); 12 | return lent/(lent + notLent); 13 | } 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/chapter14/lending-ratio-1.js: -------------------------------------------------------------------------------- 1 | function lendingRatio(books) { 2 | var bookItems = flatMap(books, "bookItems"); 3 | var stats = _.reduce(bookItems, function(res, item) { 4 | if(_.get(item, "isLent")) { 5 | res.lent = res.lent + 1; 6 | } else { 7 | res.notLent = res.notLent + 1; 8 | } 9 | return res; 10 | }, {notLent: 0, lent:0}); 11 | return stats.lent/(stats.lent + stats.notLent); 12 | } 13 | -------------------------------------------------------------------------------- /src/chapter14/lending-ratio.js: -------------------------------------------------------------------------------- 1 | function lendingRatio(books) { 2 | var bookItems = flatMap(books, "bookItems"); 3 | var stats = countByBoolField(bookItems, "isLent", "lent", "notLent"); 4 | return stats.lent/(stats.lent + stats.notLent); 5 | } 6 | 7 | -------------------------------------------------------------------------------- /src/chapter14/list-books.js: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "isbn": "978-1779501127", 4 | "title": "Watchmen", 5 | "bookItems": [ 6 | { 7 | "id": "book-item-1", 8 | "libId": "nyc-central-lib", 9 | "isLent": true 10 | } 11 | ] 12 | }, 13 | { 14 | "isbn": "978-1982137274", 15 | "title": "7 Habits of Highly Effective People", 16 | "bookItems": [ 17 | { 18 | "id": "book-item-123", 19 | "libId": "hudson-park-lib", 20 | "isLent": true 21 | }, 22 | { 23 | "id": "book-item-17", 24 | "libId": "nyc-central-lib", 25 | "isLent": false 26 | } 27 | ] 28 | } 29 | ] 30 | -------------------------------------------------------------------------------- /src/chapter14/remove-duplicates-0.js: -------------------------------------------------------------------------------- 1 | function removeAuthorDuplicates(book) { 2 | var authors = _.get(book, "authors"); 3 | var uniqAuthors = _.uniq(authors); 4 | return _.set(book,"authors", uniqAuthors); 5 | } 6 | 7 | -------------------------------------------------------------------------------- /src/chapter14/remove-duplicates.js: -------------------------------------------------------------------------------- 1 | function removeAuthorDuplicates(book) { 2 | return update(book, "authors", _.uniq); 3 | } 4 | 5 | -------------------------------------------------------------------------------- /src/chapter14/unwind-test.js: -------------------------------------------------------------------------------- 1 | var customer = { 2 | "customer-id": "joe", 3 | "items": [ 4 | { 5 | "item": "phone", 6 | "quantity": 1 7 | }, 8 | { 9 | "item": "pencil", 10 | "quantity": 10 11 | } 12 | ] 13 | }; 14 | 15 | var expectedRes = [ 16 | { 17 | "customer-id": "joe", 18 | "items": { 19 | "item": "phone", 20 | "quantity": 1 21 | } 22 | }, 23 | { 24 | "customer-id": "joe", 25 | "items": { 26 | "item": "pencil", 27 | "quantity": 10 28 | } 29 | } 30 | ] 31 | 32 | _.isEqual(unwind(customer, "items"), expectedRes) 33 | 34 | -------------------------------------------------------------------------------- /src/chapter14/unwind.js: -------------------------------------------------------------------------------- 1 | function unwind(map, field) { 2 | var arr = _.get(map, field); 3 | return _.map(arr, function(elem) { 4 | return _.set(map, field, elem); 5 | }); 6 | } 7 | -------------------------------------------------------------------------------- /src/chapter14/update-inc.js: -------------------------------------------------------------------------------- 1 | var m = { 2 | "position": "manager", 3 | "income": 100000 4 | }; 5 | update(m, "income", function(x) { 6 | return x * 2; 7 | }); 8 | // → {"position": "manager", "income": 200000} 9 | -------------------------------------------------------------------------------- /src/chapter14/update.js: -------------------------------------------------------------------------------- 1 | function update(map, path, fun) { 2 | var currentValue = _.get(map, path); 3 | var nextValue = fun(currentValue); 4 | return _.set(map, path, nextValue); 5 | } 6 | -------------------------------------------------------------------------------- /src/chapter15/.#catalog-data.js: -------------------------------------------------------------------------------- 1 | viebel@yehonathan-XPS-15-9500.2086579:1632753356 -------------------------------------------------------------------------------- /src/chapter15/data-file-name.js: -------------------------------------------------------------------------------- 1 | var capturedDataFolder = "test-data"; // <1> 2 | function dataFilePath(context) { 3 | var uuid = generateUUID(); // <2> 4 | return capturedDataFolder + "/" + context + "-" + ".json"; // <3> 5 | } 6 | -------------------------------------------------------------------------------- /src/chapter15/digit-capture.js: -------------------------------------------------------------------------------- 1 | function nthDigit(a, n) { 2 | console.log(a); 3 | console.log(n); 4 | return Math.floor((a / (Math.pow(10, n - 1)))) % 10; 5 | } 6 | -------------------------------------------------------------------------------- /src/chapter15/digit.js: -------------------------------------------------------------------------------- 1 | function nthDigit(a, n) { 2 | return Math.floor((a / (Math.pow(10, n - 1)))) % 10; 3 | } 4 | -------------------------------------------------------------------------------- /src/chapter15/dump-data-ident.js: -------------------------------------------------------------------------------- 1 | function dumpData(data, context) { 2 | var path = dataFilePath(context); 3 | var content = JSON.stringify(data, null, 2); // <1> <2> 4 | fs.writeFile(path, content, function () { 5 | console.log("Data for " + context + "stored in: " + path); 6 | }); 7 | } 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/chapter15/dump-data.js: -------------------------------------------------------------------------------- 1 | function dumpData(data, context) { 2 | var path = dataFilePath(context); 3 | var content = JSON.stringify(data); 4 | fs.writeFile(path, content, function () { // <1> <2> 5 | console.log("Data for " + context + "stored in: " + path); // <3> 6 | }); 7 | } 8 | -------------------------------------------------------------------------------- /src/chapter15/prefix-capture-json.js: -------------------------------------------------------------------------------- 1 | function hasWordStartingWith(sentence, prefix) { 2 | console.log(JSON.stringify(sentence)); 3 | console.log(JSON.stringify(prefix)); 4 | var words = sentence.split(" "); 5 | return _.find(words, function(word) { 6 | return word.startsWith(prefix); 7 | }) != null; 8 | } 9 | -------------------------------------------------------------------------------- /src/chapter15/prefix-capture.js: -------------------------------------------------------------------------------- 1 | function hasWordStartingWith(sentence, prefix) { 2 | console.log(sentence); 3 | console.log(prefix); 4 | var words = sentence.split(" "); 5 | return _.find(words, function(word) { 6 | return word.startsWith(prefix); 7 | }) != null; 8 | } 9 | -------------------------------------------------------------------------------- /src/chapter15/prefix-improved.js: -------------------------------------------------------------------------------- 1 | function hasWordStartingWith(sentence, prefix) { 2 | var sentenceLowerCase = sentence.toLowerCase(); 3 | var prefixLowerCase = prefix.toLowerCase(); 4 | var prefixRegExp = new RegExp("\\b" + prefixLowerCase); // <1> 5 | return sentenceLowerCase.match(prefixRegExp) != null; 6 | } 7 | 8 | -------------------------------------------------------------------------------- /src/chapter15/prefix-run-1.js: -------------------------------------------------------------------------------- 1 | hasWordStartingWith("I like the word \"reproducibility\"", "li"); 2 | // It returns true 3 | // It displays the following two lines: 4 | // I like the word "reproducibility" 5 | // li 6 | -------------------------------------------------------------------------------- /src/chapter15/prefix.js: -------------------------------------------------------------------------------- 1 | function hasWordStartingWith(sentence, prefix) { 2 | var words = sentence.split(" "); 3 | return _.find(words, function(word) { 4 | return word.startsWith(prefix); 5 | }) != null; 6 | } 7 | -------------------------------------------------------------------------------- /src/chapter15/read-data.js: -------------------------------------------------------------------------------- 1 | function readData(path) { 2 | return JSON.parse(fs.readFileSync(path)); 3 | } 4 | -------------------------------------------------------------------------------- /src/chapter15/search-capture-log.json: -------------------------------------------------------------------------------- 1 | [{"booksByIsbn":{"978-1982137274":{"isbn":"978-1982137274", 2 | "title":"7 Habits of Highly Effective People","authorIds": 3 | ["sean-covey","stephen-covey"]},"978-1779501127":{"isbn": 4 | "978-1779501127","title":"Watchmen","publicationYear":1987, 5 | "authorIds":["alan-moore","dave-gibbons"]}},"authorsById": 6 | {"stephen-covey":{"name":"Stephen Covey","bookIsbns": 7 | ["978-1982137274"]},"sean-covey":{"name":"Sean Covey", 8 | "bookIsbns":["978-1982137274"]},"dave-gibbons": 9 | {"name":"Dave Gibbons","bookIsbns":["978-1779501127"]}, 10 | "alan-moore":{"name":"Alan Moore","bookIsbns": 11 | ["978-1779501127"]}}},"Habit"] 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/chapter15/search-capture-log.txt: -------------------------------------------------------------------------------- 1 | Data for searchBooksByTitle stored in 2 | test-data/searchBooksByTitle-68e57c85-2213-471a-8442-c4516e83d786.json 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/chapter15/search-capture.js: -------------------------------------------------------------------------------- 1 | Catalog.searchBooksByTitle = function(catalogData, query) { 2 | console.log(JSON.stringify(catalogData)); 3 | console.log(JSON.stringify(query)); 4 | var allBooks = _.get(catalogData, "booksByIsbn"); 5 | var queryLowerCased = query.toLowerCase(); 6 | var matchingBooks = _.filter(allBooks, function(book) { 7 | return _.get(book, "title") 8 | .toLowerCase() 9 | .startsWith(queryLowerCased); 10 | }); 11 | var bookInfos = _.map(matchingBooks, function(book) { 12 | return Catalog.bookInfo(catalogData, book); 13 | }); 14 | return bookInfos; 15 | }; 16 | -------------------------------------------------------------------------------- /src/chapter15/search-dump.js: -------------------------------------------------------------------------------- 1 | Catalog.searchBooksByTitle = function(catalogData, query) { 2 | dumpData([catalogData, query], 'searchBooksByTitle'); 3 | var allBooks = _.get(catalogData, "booksByIsbn"); 4 | var queryLowerCased = query.toLowerCase(); 5 | var matchingBooks = _.filter(allBooks, function(book) { 6 | return _.get(book, "title") 7 | .toLowerCase() 8 | .startsWith(queryLowerCased); 9 | }); 10 | var bookInfos = _.map(matchingBooks, function(book) { 11 | return Catalog.bookInfo(catalogData, book); 12 | }); 13 | return bookInfos; 14 | }; 15 | 16 | -------------------------------------------------------------------------------- /src/chapter15/search-improved.js: -------------------------------------------------------------------------------- 1 | Catalog.searchBooksByTitle = function(catalogData, query) { 2 | console.log(JSON.stringify(catalogData)); 3 | console.log(JSON.stringify(query)); 4 | var allBooks = _.get(catalogData, "booksByIsbn"); 5 | var matchingBooks = _.filter(allBooks, function(book) { 6 | return hasWordStartingWith(_.get(book, "title"), query); 7 | }); 8 | var bookInfos = _.map(matchingBooks, function(book) { 9 | return Catalog.bookInfo(catalogData, book); 10 | }); 11 | return bookInfos; 12 | }; 13 | -------------------------------------------------------------------------------- /src/chapter15/search-reproduce.js: -------------------------------------------------------------------------------- 1 | var catalogData = {"booksByIsbn":{"978-1982137274": 2 | {"isbn":"978-1982137274","title":"7 Habits of Highly Effective People", 3 | "authorIds":["sean-covey","stephen-covey"]},"978-1779501127": 4 | {"isbn":"978-1779501127","title":"Watchmen","publicationYear":1987, 5 | "authorIds":["alan-moore","dave-gibbons"]}},"authorsById": 6 | {"stephen-covey":{"name":"Stephen Covey","bookIsbns": 7 | ["978-1982137274"]},"sean-covey":{"name":"Sean Covey","bookIsbns": 8 | ["978-1982137274"]},"dave-gibbons":{"name":"Dave Gibbons","bookIsbns": 9 | ["978-1779501127"]},"alan-moore":{"name":"Alan Moore","bookIsbns": 10 | ["978-1779501127"]}}}; 11 | var query = "Watch"; 12 | 13 | Catalog.searchBooksByTitle(catalogData, query); 14 | -------------------------------------------------------------------------------- /src/chapter15/search-test-1.js: -------------------------------------------------------------------------------- 1 | Catalog.searchBooksByTitle(catalogData, 'Habit'); 2 | // → [] 3 | -------------------------------------------------------------------------------- /src/chapter15/search-test-2.js: -------------------------------------------------------------------------------- 1 | Catalog.searchBooksByTitle(catalogData, 'Habit'); 2 | // → [ { "title": "7 Habits of Highly Effective People", …}] 3 | 4 | -------------------------------------------------------------------------------- /src/chapter15/search-test-multiple-fail.js: -------------------------------------------------------------------------------- 1 | var data = readData('test-data/searchBooksByTitle-68e57c85-2213-471a-8442-c4516e83d786.json'); 2 | var catalogData = data[0]; 3 | var queries = ["abit", "bit", "7 abit", "habit of"]; 4 | var expectedResult = [ ]; 5 | 6 | _.every(queries, function(query) { 7 | var result = Catalog.searchBooksByTitle(catalogData, query); 8 | return _.isEqual(result, expectedResult); 9 | }); 10 | // → [true, true, true, true] 11 | -------------------------------------------------------------------------------- /src/chapter15/search-test-multiple.js: -------------------------------------------------------------------------------- 1 | var data = readData('test-data/searchBooksByTitle-68e57c85-2213-471a-8442-c4516e83d786.json'); 2 | var catalogData = data[0]; 3 | var queries = ["Habit", "habit", "7 Habit", "habits of"]; 4 | var expectedResult = [ 5 | { 6 | "authorNames": [ 7 | "Sean Covey", 8 | "Stephen Covey", 9 | ], 10 | "isbn": "978-1982137274", 11 | "title": "7 Habits of Highly Effective People", 12 | } 13 | ]; 14 | 15 | _.every(queries, function(query) { 16 | var result = Catalog.searchBooksByTitle(catalogData, query); 17 | return _.isEqual(result, expectedResult); 18 | }); 19 | // → [true, true, true, true] 20 | -------------------------------------------------------------------------------- /src/chapter15/search-test-paste.js: -------------------------------------------------------------------------------- 1 | var catalogData = {"booksByIsbn":{"978-1982137274":{"isbn":"978-1982137274", 2 | "title":"7 Habits of Highly Effective People","authorIds":["sean-covey", 3 | "stephen-covey"]},"978-1779501127":{"isbn":"978-1779501127","title": 4 | "Watchmen","publicationYear":1987,"authorIds":["alan-moore", 5 | "dave-gibbons"]}},"authorsById":{"stephen-covey":{"name": 6 | "Stephen Covey","bookIsbns":["978-1982137274"]},"sean-covey": 7 | {"name":"Sean Covey","bookIsbns":["978-1982137274"]},"dave-gibbons": 8 | {"name":"Dave Gibbons","bookIsbns":["978-1779501127"]},"alan-moore": 9 | {"name":"Alan Moore","bookIsbns":["978-1779501127"]}}}; 10 | var query = "Habit"; 11 | 12 | var result = Catalog.searchBooksByTitle(catalogData, query); 13 | var expectedResult = [ 14 | { 15 | "authorNames": [ 16 | "Sean Covey", 17 | "Stephen Covey", 18 | ], 19 | "isbn": "978-1982137274", 20 | "title": "7 Habits of Highly Effective People", 21 | } 22 | ]; 23 | 24 | _.isEqual(result, expectedResult); 25 | // → true 26 | 27 | -------------------------------------------------------------------------------- /src/chapter15/search-test.js: -------------------------------------------------------------------------------- 1 | var data = readData('test-data/searchBooksByTitle-68e57c85-2213-471a-8442-c4516e83d786.json'); 2 | var catalogData = data[0]; 3 | var query = data[1]; 4 | 5 | var result = Catalog.searchBooksByTitle(catalogData, query); 6 | var expectedResult = [ 7 | { 8 | "authorNames": [ 9 | "Sean Covey", 10 | "Stephen Covey", 11 | ], 12 | "isbn": "978-1982137274", 13 | "title": "7 Habits of Highly Effective People", 14 | } 15 | ]; 16 | 17 | _.isEqual(result, expectedResult); 18 | // → false 19 | -------------------------------------------------------------------------------- /src/chapter15/serialize-string.js: -------------------------------------------------------------------------------- 1 | console.log(JSON.stringify("I like the word \"reproducibility\"")); // <1> 2 | // → "I like the word \"reproducibility\"" 3 | -------------------------------------------------------------------------------- /src/chapter15/watch-output.txt: -------------------------------------------------------------------------------- 1 | {"booksByIsbn":{"978-1982137274":{"isbn":"978-1982137274"\ 2 | ,"title":"7 Habits of Highly Effective People","authorIds":\ 3 | ["sean-covey","stephen-covey"]},"978-1779501127":{"isbn":"978-1779501127"\ 4 | ,"title":"Watchmen","publicationYear":1987,"authorIds":["alan-moore",\ 5 | "dave-gibbons"]}},"authorsById":{"stephen-covey":{"name":"Stephen Covey",\ 6 | "bookIsbns":["978-1982137274"]},"sean-covey":{"name":"Sean Covey",\ 7 | "bookIsbns":["978-1982137274"]},"dave-gibbons":{"name":"Dave Gibbons",\ 8 | "bookIsbns":["978-1779501127"]},"alan-moore":{"name":"Alan Moore",\ 9 | "bookIsbns":["978-1779501127"]}}} 10 | "Watch" 11 | --------------------------------------------------------------------------------