├── .gitattributes ├── .gitignore ├── .ionide └── symbolCache.db ├── Article ├── article.md └── pics │ ├── FieldIntelliSense.png │ ├── FieldValidation.png │ └── LambdaIntellisense.png ├── README.md ├── Typesafe in-memory database.njsproj ├── Typesafe in-memory database.sln ├── app.ts ├── database.ts ├── flattener.ts.notyet ├── out ├── app.d.ts ├── app.js ├── app.js.map ├── database.d.ts ├── database.js ├── database.js.map ├── sample.d.ts ├── sample.js ├── sample.js.map ├── tsconfig.tsbuildinfo ├── utils.d.ts ├── utils.js └── utils.js.map ├── package-lock.json ├── package.json ├── sample1.ts ├── sample2.ts ├── tsconfig.json └── utils.ts /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.rsuser 8 | *.suo 9 | *.user 10 | *.userosscache 11 | *.sln.docstates 12 | 13 | # User-specific files (MonoDevelop/Xamarin Studio) 14 | *.userprefs 15 | 16 | # Build results 17 | [Dd]ebug/ 18 | [Dd]ebugPublic/ 19 | [Rr]elease/ 20 | [Rr]eleases/ 21 | x64/ 22 | x86/ 23 | [Aa][Rr][Mm]/ 24 | [Aa][Rr][Mm]64/ 25 | bld/ 26 | [Bb]in/ 27 | [Oo]bj/ 28 | [Ll]og/ 29 | 30 | # Visual Studio 2015/2017 cache/options directory 31 | .vs/ 32 | # Uncomment if you have tasks that create the project's static files in wwwroot 33 | #wwwroot/ 34 | 35 | # Visual Studio 2017 auto generated files 36 | Generated\ Files/ 37 | 38 | # MSTest test Results 39 | [Tt]est[Rr]esult*/ 40 | [Bb]uild[Ll]og.* 41 | 42 | # NUNIT 43 | *.VisualState.xml 44 | TestResult.xml 45 | 46 | # Build Results of an ATL Project 47 | [Dd]ebugPS/ 48 | [Rr]eleasePS/ 49 | dlldata.c 50 | 51 | # Benchmark Results 52 | BenchmarkDotNet.Artifacts/ 53 | 54 | # .NET Core 55 | project.lock.json 56 | project.fragment.lock.json 57 | artifacts/ 58 | 59 | # StyleCop 60 | StyleCopReport.xml 61 | 62 | # Files built by Visual Studio 63 | *_i.c 64 | *_p.c 65 | *_h.h 66 | *.ilk 67 | *.meta 68 | *.obj 69 | *.iobj 70 | *.pch 71 | *.pdb 72 | *.ipdb 73 | *.pgc 74 | *.pgd 75 | *.rsp 76 | *.sbr 77 | *.tlb 78 | *.tli 79 | *.tlh 80 | *.tmp 81 | *.tmp_proj 82 | *_wpftmp.csproj 83 | *.log 84 | *.vspscc 85 | *.vssscc 86 | .builds 87 | *.pidb 88 | *.svclog 89 | *.scc 90 | 91 | # Chutzpah Test files 92 | _Chutzpah* 93 | 94 | # Visual C++ cache files 95 | ipch/ 96 | *.aps 97 | *.ncb 98 | *.opendb 99 | *.opensdf 100 | *.sdf 101 | *.cachefile 102 | *.VC.db 103 | *.VC.VC.opendb 104 | 105 | # Visual Studio profiler 106 | *.psess 107 | *.vsp 108 | *.vspx 109 | *.sap 110 | 111 | # Visual Studio Trace Files 112 | *.e2e 113 | 114 | # TFS 2012 Local Workspace 115 | $tf/ 116 | 117 | # Guidance Automation Toolkit 118 | *.gpState 119 | 120 | # ReSharper is a .NET coding add-in 121 | _ReSharper*/ 122 | *.[Rr]e[Ss]harper 123 | *.DotSettings.user 124 | 125 | # JustCode is a .NET coding add-in 126 | .JustCode 127 | 128 | # TeamCity is a build add-in 129 | _TeamCity* 130 | 131 | # DotCover is a Code Coverage Tool 132 | *.dotCover 133 | 134 | # AxoCover is a Code Coverage Tool 135 | .axoCover/* 136 | !.axoCover/settings.json 137 | 138 | # Visual Studio code coverage results 139 | *.coverage 140 | *.coveragexml 141 | 142 | # NCrunch 143 | _NCrunch_* 144 | .*crunch*.local.xml 145 | nCrunchTemp_* 146 | 147 | # MightyMoose 148 | *.mm.* 149 | AutoTest.Net/ 150 | 151 | # Web workbench (sass) 152 | .sass-cache/ 153 | 154 | # Installshield output folder 155 | [Ee]xpress/ 156 | 157 | # DocProject is a documentation generator add-in 158 | DocProject/buildhelp/ 159 | DocProject/Help/*.HxT 160 | DocProject/Help/*.HxC 161 | DocProject/Help/*.hhc 162 | DocProject/Help/*.hhk 163 | DocProject/Help/*.hhp 164 | DocProject/Help/Html2 165 | DocProject/Help/html 166 | 167 | # Click-Once directory 168 | publish/ 169 | 170 | # Publish Web Output 171 | *.[Pp]ublish.xml 172 | *.azurePubxml 173 | # Note: Comment the next line if you want to checkin your web deploy settings, 174 | # but database connection strings (with potential passwords) will be unencrypted 175 | *.pubxml 176 | *.publishproj 177 | 178 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 179 | # checkin your Azure Web App publish settings, but sensitive information contained 180 | # in these scripts will be unencrypted 181 | PublishScripts/ 182 | 183 | # NuGet Packages 184 | *.nupkg 185 | # The packages folder can be ignored because of Package Restore 186 | **/[Pp]ackages/* 187 | # except build/, which is used as an MSBuild target. 188 | !**/[Pp]ackages/build/ 189 | # Uncomment if necessary however generally it will be regenerated when needed 190 | #!**/[Pp]ackages/repositories.config 191 | # NuGet v3's project.json files produces more ignorable files 192 | *.nuget.props 193 | *.nuget.targets 194 | 195 | # Microsoft Azure Build Output 196 | csx/ 197 | *.build.csdef 198 | 199 | # Microsoft Azure Emulator 200 | ecf/ 201 | rcf/ 202 | 203 | # Windows Store app package directories and files 204 | AppPackages/ 205 | BundleArtifacts/ 206 | Package.StoreAssociation.xml 207 | _pkginfo.txt 208 | *.appx 209 | 210 | # Visual Studio cache files 211 | # files ending in .cache can be ignored 212 | *.[Cc]ache 213 | # but keep track of directories ending in .cache 214 | !?*.[Cc]ache/ 215 | 216 | # Others 217 | ClientBin/ 218 | ~$* 219 | *~ 220 | *.dbmdl 221 | *.dbproj.schemaview 222 | *.jfm 223 | *.pfx 224 | *.publishsettings 225 | orleans.codegen.cs 226 | 227 | # Including strong name files can present a security risk 228 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 229 | #*.snk 230 | 231 | # Since there are multiple workflows, uncomment next line to ignore bower_components 232 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 233 | #bower_components/ 234 | 235 | # RIA/Silverlight projects 236 | Generated_Code/ 237 | 238 | # Backup & report files from converting an old project file 239 | # to a newer Visual Studio version. Backup files are not needed, 240 | # because we have git ;-) 241 | _UpgradeReport_Files/ 242 | Backup*/ 243 | UpgradeLog*.XML 244 | UpgradeLog*.htm 245 | ServiceFabricBackup/ 246 | *.rptproj.bak 247 | 248 | # SQL Server files 249 | *.mdf 250 | *.ldf 251 | *.ndf 252 | 253 | # Business Intelligence projects 254 | *.rdl.data 255 | *.bim.layout 256 | *.bim_*.settings 257 | *.rptproj.rsuser 258 | *- Backup*.rdl 259 | 260 | # Microsoft Fakes 261 | FakesAssemblies/ 262 | 263 | # GhostDoc plugin setting file 264 | *.GhostDoc.xml 265 | 266 | # Node.js Tools for Visual Studio 267 | .ntvs_analysis.dat 268 | node_modules/ 269 | 270 | # Visual Studio 6 build log 271 | *.plg 272 | 273 | # Visual Studio 6 workspace options file 274 | *.opt 275 | 276 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 277 | *.vbw 278 | 279 | # Visual Studio LightSwitch build output 280 | **/*.HTMLClient/GeneratedArtifacts 281 | **/*.DesktopClient/GeneratedArtifacts 282 | **/*.DesktopClient/ModelManifest.xml 283 | **/*.Server/GeneratedArtifacts 284 | **/*.Server/ModelManifest.xml 285 | _Pvt_Extensions 286 | 287 | # Paket dependency manager 288 | .paket/paket.exe 289 | paket-files/ 290 | 291 | # FAKE - F# Make 292 | .fake/ 293 | 294 | # JetBrains Rider 295 | .idea/ 296 | *.sln.iml 297 | 298 | # CodeRush personal settings 299 | .cr/personal 300 | 301 | # Python Tools for Visual Studio (PTVS) 302 | __pycache__/ 303 | *.pyc 304 | 305 | # Cake - Uncomment if you are using it 306 | # tools/** 307 | # !tools/packages.config 308 | 309 | # Tabs Studio 310 | *.tss 311 | 312 | # Telerik's JustMock configuration file 313 | *.jmconfig 314 | 315 | # BizTalk build output 316 | *.btp.cs 317 | *.btm.cs 318 | *.odx.cs 319 | *.xsd.cs 320 | 321 | # OpenCover UI analysis results 322 | OpenCover/ 323 | 324 | # Azure Stream Analytics local run output 325 | ASALocalRun/ 326 | 327 | # MSBuild Binary and Structured Log 328 | *.binlog 329 | 330 | # NVidia Nsight GPU debugger configuration file 331 | *.nvuser 332 | 333 | # MFractors (Xamarin productivity tool) working folder 334 | .mfractor/ 335 | 336 | # Local History for Visual Studio 337 | .localhistory/ 338 | 339 | # BeatPulse healthcheck temp database 340 | healthchecksdb 341 | yarn.lock 342 | -------------------------------------------------------------------------------- /.ionide/symbolCache.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hoppinger/TypeScript-typesafe-relational-processor/d6bf8c4385004a62ea19e6a93553b0507942c3f7/.ionide/symbolCache.db -------------------------------------------------------------------------------- /Article/article.md: -------------------------------------------------------------------------------- 1 | # A type-safe, in-memory database for TypeScript 2 | _By Dr. Giuseppe Maggiore_ 3 | 4 | 5 | In modern Single Page Application (SPA) development, we have a lot of data processing we need to perform locally. One of the biggest triggers for this article, is the following scenario\: 6 | 7 | > For an awesome [online food ordering platform](https://beren.nl), we receive the following data from a Graph API\: `Restaurants -> Products -> [Categories, Options]` (thus we get a list of restaurants, each with a list of products inside, and each product contains a list of categories it falls under, and the options available for that product). 8 | > This appears to be the fastest query that the API supports, also because products are found in many categories (thus we get less duplicates!). 9 | > The React renderer needs to sort products by category, and we also don't care about the restaurants because we are fetching the data for a single restaurant anyway. We need to turn the result of the query around, so that it looks like this\: `Categories -> Products -> Options`. 10 | > And so, a tale of `Map`s, `Set`s, `flatMap`, etc. begins. 11 | 12 | The resulting data processing code looks roughly like this\: 13 | 14 | ```ts 15 | let res = restaurants.filter(restaurant => (restaurant.Restaurant_WaitingTimes.first() != undefined) && (restaurant.Restaurant_WaitingTimes.first().WaitingTimes != null)) 16 | .map(restaurant => ({...restaurant, 17 | PostCodeRanges: restaurant.Restaurant_PostCodeRange.flatMap(rpcr => rpcr.PostCodeRange.filter(pcr => pcr.Active).map(pcr => 18 | DeliveryClosingTimes: restaurant.Restaurant_DeliveryClosingTime.flatMap(r_dct => r_dct.DeliveryClosingTime.map(dct => 19 | DeliveryOpeningTimes: restaurant.Restaurant_DeliveryTimes.flatMap(r_dt => r_dt.DeliveryTimes.map(dt => ...), 20 | PickupClosingTimes: restaurant.Restaurant_PickupClosingTime.flatMap(r_pct => r_pct.PickupClosingTime.map(pct => 21 | ...))).toList(), 22 | PickupOpeningTimes: restaurant.Restaurant_PickupTimes.flatMap(r_pt => r_pt.PickupTimes.map(pt => 23 | DeliveryPayMethods: restaurant.DeliveryRestaurant_DeliveryPayMethod.sortBy(dr_dpm => dr_dpm.DeliveryPayMethodId).flatMap(dr_dpm => dr_dpm.PayMethod.map(pm => ({Method: pm.Method, DisplayTitle: pm.DisplayTitle}))).toList(), 24 | PickUpPayMethods: restaurant.PickUpRestaurant_PickUpPayMethod.sortBy(pur_pupm => pur_pupm.PickUpPayMethodId).flatMap(pur_pupm => pur_pupm.PayMethod.map(pm => ({Method: pm.Method, DisplayTitle: pm.DisplayTitle}))).toList() 25 | ...) 26 | 27 | ``` 28 | 29 | Notice the nesting of multiple `flatMap`, `map`, and `filter` operators. While I am personally very happy that developers in my team are using functional code, which is a bit more guaranteed to work properly than working with nested imperative containers, I have to say\: this stuff is complex! 30 | 31 | Perhaps you recognize this sort of challenge. While there may not be anything exceptionally complex about this situation, and the code that is needed in order to tackle it, it will still be quite a lot of tedious work. The distance between explaining what needs to be done, and the code that does it, is also quite annoying\: ideally, we want declarative code that almost reads like a description of a user story, but in this case, programmers reading and modifying the code must drown in technicality about data structure processing and lookups. *This situation is not ideal*. 32 | 33 | For this reason, I embarked on a journey to define a library for declarative, client\-side data processing, that would make this sort of operation less painful, with easier to read code, and last but not least with all the support possible from the compiler. 34 | 35 | 36 | ## The underlying idea 37 | Instead of processing data in this "raw" format, how about we sort it first into a sort of standard, and only then we reformat it and process it however we see fit? And as a standard, how about taking inspiration from both relational databases and Graph APIs? This way, we store data in a proven manner (entities and relations!) and we then access the data through simple, logical operators that convey the fact that entities and relations are indeed a graph. 38 | 39 | The underlying containers will be offered by the awesome _immutablejs_ library, which features one of (the?) most complete collection libraries available nowadays on the market (and not only in the JavaScript world, but that is only my own opinion). 40 | 41 | ### A first example 42 | We start by setting up a very simple database, comprised only of people. The definition of the types that make up the entities and the whole database will then look like this\: 43 | 44 | ```ts 45 | interface Person { 46 | Name: string 47 | Surname: string 48 | Age: number 49 | } 50 | ``` 51 | 52 | Next, we define the "database" itself. We define a type (`MyEntities`) with a field for each table we want to store. Tables are declared with the generic `Entity` type, which takes as input the primary keys of the entity, the fields, and the relations\: 53 | 54 | ```ts 55 | interface MyEntities { 56 | People: Entity<{ PersonId: number }, Person, { 57 | }, MyEntities> 58 | } 59 | ``` 60 | 61 | Now, we fill up the database by creating an instance of it with sample data\: 62 | 63 | ```ts 64 | const PersonId = Record({ PersonId: 0 }) 65 | 66 | const myEntities: MyEntities = { 67 | People: Entity()(Map, Person>() 68 | .set(PersonId({ PersonId: 0 }), { Name: "John", Surname: "Doe", Age: 27 }) 69 | .set(PersonId({ PersonId: 1 }), { Name: "Jane", Surname: "Doe", Age: 31 }) 70 | .set(PersonId({ PersonId: 2 }), { Name: "Giuseppe", Surname: "Rossi", Age: 35}) 71 | .set(PersonId({ PersonId: 3 }), { Name: "Giulia", Surname: "Verdi", Age: 34 }) 72 | .set(PersonId({ PersonId: 4 }), { Name: "Reby", Surname: "Rossi", Age: 7 }) 73 | .set(PersonId({ PersonId: 5 }), { Name: "Rechy", Surname: "Rossi", Age: 5 }) 74 | .set(PersonId({ PersonId: 6 }), { Name: "Francis", Surname: "Dee Jay", Age: 35 }) 75 | , 76 | { 77 | }) 78 | } 79 | ``` 80 | 81 | > One minor oddity\: we have to use `Record` from _immutablejs_, otherwise the underlying constructs will complain. If only `TypeScript` supported *structural equality*... 82 | 83 | Now we can instantiate the actual database, and run some queries on it! 84 | 85 | ```ts 86 | const db: Database = Database(myEntities) 87 | ``` 88 | 89 | Let's start with something easy. For each `Person`, we want their `Name` and `Surname`, thereby discarding the `Age`\: 90 | 91 | ```ts 92 | const q0 = db.from("People").select("Name", "Surname") 93 | ``` 94 | 95 | This results in\: 96 | 97 | ```json 98 | [ 99 | { 100 | "Name": "John", 101 | "Surname": "Doe" 102 | }, 103 | { 104 | "Name": "Jane", 105 | "Surname": "Doe" 106 | }, 107 | ... 108 | ] 109 | ``` 110 | 111 | Let's filter all people with at least 18 years of `Age`\: 112 | 113 | 114 | ```ts 115 | const q1 = db.from("People").filter(p => p.Age >= 18) 116 | ``` 117 | 118 | This results in\: 119 | 120 | ```json 121 | [ 122 | { 123 | "Name": "John", 124 | "Surname": "Doe", 125 | "Age": 27 126 | }, 127 | { 128 | "Name": "Jane", 129 | "Surname": "Doe", 130 | "Age": 31 131 | }, 132 | { 133 | "Name": "Giuseppe", 134 | "Surname": "Rossi", 135 | "Age": 35 136 | }, 137 | { 138 | "Name": "Giulia", 139 | "Surname": "Verdi", 140 | "Age": 34 141 | }, 142 | { 143 | "Name": "Francis", 144 | "Surname": "Dee Jay", 145 | "Age": 35 146 | } 147 | ] 148 | ``` 149 | 150 | We can obviously combine different operators in a chain. For example, after filtering, we can just select the `Name` attribute of each result\: 151 | 152 | ```ts 153 | const q2 = db.from("People").filter(p => p.Age >= 18).select("Name") 154 | ``` 155 | 156 | Resulting in\: 157 | 158 | ```json 159 | [ 160 | { 161 | "Name": "John" 162 | }, 163 | { 164 | "Name": "Jane" 165 | }, 166 | { 167 | "Name": "Giuseppe" 168 | }, 169 | { 170 | "Name": "Giulia" 171 | }, 172 | { 173 | "Name": "Francis" 174 | } 175 | ] 176 | ``` 177 | 178 | We might also decide to rename fields. For example, suppose we need the `Name` of each person to become the `FirstName` attribute\: 179 | 180 | ```ts 181 | const q3 = db.from("People").fieldAs("Name", "FirstName") 182 | ``` 183 | 184 | The result reflects this change in type\: 185 | 186 | ```json 187 | [ 188 | { 189 | "Surname": "Doe", 190 | "Age": 27, 191 | "FirstName": "John" 192 | }, 193 | { 194 | "Surname": "Doe", 195 | "Age": 31, 196 | "FirstName": "Jane" 197 | }, 198 | ... 199 | ] 200 | ``` 201 | 202 | Finally, we can mix all of these things together in a very big query. Imagination is the limit, not the library :) 203 | 204 | ```ts 205 | const q4 = db.from("People").fieldAs("Name", "FirstName").select("FirstName").filter(p => p.FirstName.startsWith("Giu")) 206 | ``` 207 | 208 | Pretty neat, eh? 209 | 210 | 211 | ### How about type safety though? 212 | So far, you might have noticed that we are using a lot of strings in our structures. Fortunately, TypeScript can be programmed in such a way as to understand that the strings we use for some inputs must be constrained to the keys/attributes of given types. This means that TypeScript will offer both input (via IntelliSense) and compiler validation to our code. 213 | 214 | Here are a few of pictures illustrating this\: 215 | 216 | ![IntelliSense on fields](./pics/FieldIntelliSense.png) 217 | 218 | ![Field validation](./pics/FieldValidation.png) 219 | 220 | ![IntelliSense on lambda's](./pics/LambdaIntellisense.png) 221 | 222 | Notice that every sort of validation that could be performed, is performed. The IDE is able to give us advice (and the compiler will actually enforce it, so this is not just cosmetic!) on available fields, will disallow non\-existing fields, and will even compose the types of the (intermediate) results such as the fact that a field rename removes the original field and adds the new one. 223 | 224 | Making *structural* mistakes with this kind of support is close to impossible, and requires a lot of discipline and motivation :) 225 | 226 | ## A beefier example 227 | 228 | Let us now consider a more complex example, where we have three entities (`Person`, `Address`, and `City`), related to each other in the expected way (people live at addresses, which are located at cities). 229 | 230 | We start with a definition of the datatypes of the database\: 231 | 232 | ```ts 233 | interface Person { 234 | Name: string 235 | Surname: string 236 | Age: number 237 | } 238 | 239 | interface Address { 240 | Street: string 241 | Number: number 242 | Postcode: string 243 | } 244 | 245 | interface City { 246 | Name: string 247 | Population: number 248 | } 249 | ``` 250 | 251 | We then define all the entities and relations of the database\: 252 | 253 | ```ts 254 | interface MyEntities { 255 | People: Entity<{ PersonId: number }, Person, { 256 | Addresses: Relation 257 | }, MyEntities> 258 | Addresses: Entity<{ AddressId: number }, Address, { 259 | Cities: Relation, 260 | People: Inverted["Addresses"]> 261 | }, MyEntities> 262 | Cities: Entity<{ CityId: number }, City, { 263 | Addresses: Inverted["Cities"]> 264 | }, MyEntities> 265 | } 266 | ``` 267 | 268 | Notice that we are now specifying, for each entity, its relations. A relation can be defined directly, for example `Relation` goes from `People` to `Addresses`, and 1 address is shared by multiple people. A relation can also be defined as the inversion of another relation, to avoid repetition; for example, `Inverted["Addresses"]>` simply fetches the `Addresses` relation of `People`, and turns it around. 269 | 270 | We now instantiate all entities and relationships. I usually prefer to start with the relationships, but it is a matter of taste, and moreover this bit of the code should be automated away by a database setup utility that does this from the result of an API! 271 | 272 | ```ts 273 | const PersonId = Record({ PersonId: 0 }) 274 | const AddressId = Record({ AddressId: 0 }) 275 | const CityId = Record({ CityId: 0 }) 276 | 277 | const personAddresses = Relation( 278 | Map, Set>>() 279 | .set(PersonId({ PersonId: 0 }), Set>() 280 | .add(AddressId({ AddressId: 0 }))) 281 | .set(PersonId({ PersonId: 1 }), Set>() 282 | .add(AddressId({ AddressId: 0 }))) 283 | .set(PersonId({ PersonId: 2 }), Set>() 284 | .add(AddressId({ AddressId: 1 }))) 285 | .set(PersonId({ PersonId: 3 }), Set>() 286 | .add(AddressId({ AddressId: 1 }))) 287 | .set(PersonId({ PersonId: 4 }), Set>() 288 | .add(AddressId({ AddressId: 1 }))) 289 | .set(PersonId({ PersonId: 5 }), Set>() 290 | .add(AddressId({ AddressId: 1 }))) 291 | .set(PersonId({ PersonId: 6 }), Set>() 292 | .add(AddressId({ AddressId: 2 }))) 293 | ) 294 | 295 | const addressCities = Relation( 296 | Map, Set>>() 297 | .set(AddressId({ AddressId: 0 }), Set>() 298 | .add(CityId({ CityId: 0 }))) 299 | .set(AddressId({ AddressId: 1 }), Set>() 300 | .add(CityId({ CityId: 1 }))) 301 | .set(AddressId({ AddressId: 2 }), Set>() 302 | .add(CityId({ CityId: 2 }))) 303 | ) 304 | 305 | const myEntities: MyEntities = { 306 | People: Entity()(Map, Person>() 307 | .set(PersonId({ PersonId: 0 }), { Name: "John", Surname: "Doe", Age: 27 }) 308 | .set(PersonId({ PersonId: 1 }), { Name: "Jane", Surname: "Doe", Age: 31 }) 309 | .set(PersonId({ PersonId: 2 }), { Name: "Giuseppe", Surname: "Rossi", Age: 35}) 310 | .set(PersonId({ PersonId: 3 }), { Name: "Giulia", Surname: "Verdi", Age: 34 }) 311 | .set(PersonId({ PersonId: 4 }), { Name: "Reby", Surname: "Rossi", Age: 7 }) 312 | .set(PersonId({ PersonId: 5 }), { Name: "Rechy", Surname: "Rossi", Age: 5 }) 313 | .set(PersonId({ PersonId: 6 }), { Name: "Francis", Surname: "Dee Jay", Age: 35 }) 314 | , 315 | { 316 | Addresses: personAddresses 317 | }), 318 | Addresses: Entity()(Map, Address>() 319 | .set(AddressId({ AddressId: 0 }), { Street: "Afrikaanderplein", Number: 7, Postcode: "3072 EA" }) 320 | .set(AddressId({ AddressId: 1 }), { Street: "Kalverstraat", Number: 92, Postcode: "1012 PH" }) 321 | .set(AddressId({ AddressId: 2 }), { Street: "Heidelaan", Number: 139, Postcode: "3851 EX" }) 322 | , 323 | { 324 | People: personAddresses.Inverted(), 325 | Cities: addressCities, 326 | }), 327 | // sort of random values, did not check if they are accurate :) 328 | Cities: Entity()(Map, City>() 329 | .set(CityId({ CityId: 0 }), { Name: "Rotterdam", Population: 1250000 }) 330 | .set(CityId({ CityId: 1 }), { Name: "Amsterdam", Population: 1250000 }) 331 | .set(CityId({ CityId: 2 }), { Name: "Hengelo", Population: 150000 }) 332 | , 333 | { 334 | Addresses: addressCities.Inverted() 335 | }) 336 | } 337 | ``` 338 | 339 | Notice that we are relying a lot on both the primary keys, and the `Record` wrapper that takes care of the proper comparisons and lookups between primary keys! 340 | 341 | Now we can create the database\: 342 | 343 | ```ts 344 | const db: Database = Database(myEntities) 345 | ``` 346 | 347 | And finally, we can run some queries\: 348 | 349 | Get all people whose `Name` starts with `Giu`. Rename attribute `Name` to `FirstName` in the result\: 350 | 351 | ```ts 352 | const q0 = db.from("People").fieldAs("Name", "FirstName").select("FirstName").filter(p => p.FirstName.startsWith("Giu")) 353 | ``` 354 | 355 | Resulting in\: 356 | ```json 357 | [ 358 | { 359 | "FirstName": "Giuseppe" 360 | }, 361 | { 362 | "FirstName": "Giulia" 363 | } 364 | ] 365 | ``` 366 | 367 | A much more exciting possibility arises when we start following the relations in order to compose a result that has multiple entities combined together. An example of this would be that for each person, we get their fields, but also their addresses as well. We then select only the `Street` and `Number` attributes of each address in order to show that our queries compose and nest correctly\: 368 | 369 | ```ts 370 | const q1 = db.from("People").expand(db, "Addresses", a => a.select("Street", "Number")) 371 | ``` 372 | 373 | Resulting in a nesting of addresses inside people\: 374 | 375 | ```json 376 | [ 377 | { 378 | "Name": "John", 379 | "Surname": "Doe", 380 | "Age": 27, 381 | "Addresses": [ 382 | { 383 | "Street": "Afrikaanderplein", 384 | "Number": 7 385 | } 386 | ] 387 | }, 388 | ... 389 | { 390 | "Name": "Giuseppe", 391 | "Surname": "Rossi", 392 | "Age": 35, 393 | "Addresses": [ 394 | { 395 | "Street": "Kalverstraat", 396 | "Number": 92 397 | } 398 | ] 399 | }, 400 | ... 401 | ] 402 | ``` 403 | 404 | There are multiple ways to compose entities together. For example, we could merge related entities into one larger entity with all the combined attributes, much like an `inner join` in SQL. 405 | 406 | For each person, their address, and its city, we can indeed make a single entity with all the attributes of the three input entities. Select only the `Street` and `Number` attributes of each address. Rename the `Name` attribute of the `City` to `CityName` to avoid overlap with `Person` `Name`\: 407 | 408 | ```ts 409 | const q2 = db.from("People").join(db, "Addresses", a => a.select("Street", "Number").join(db, "Cities", c => c.fieldAs("Name", "CityName"))) 410 | ``` 411 | 412 | As expected, this returns a single array of entities, with no nesting\: 413 | 414 | ```json 415 | [ 416 | { 417 | "Name": "John", 418 | "Surname": "Doe", 419 | "Age": 27, 420 | "Street": "Afrikaanderplein", 421 | "Number": 7, 422 | "Population": 1250000, 423 | "CityName": "Rotterdam" 424 | }, 425 | ... 426 | { 427 | "Name": "Giuseppe", 428 | "Surname": "Rossi", 429 | "Age": 35, 430 | "Street": "Kalverstraat", 431 | "Number": 92, 432 | "Population": 1250000, 433 | "CityName": "Amsterdam" 434 | }, 435 | ... 436 | ] 437 | ``` 438 | 439 | We can also go the other way around, thanks to our inverted relations. For each city, expand its addresses, and for each address, expand the people living there\: 440 | 441 | ```ts 442 | const q3 = db.from("Cities").expand(db, "Addresses", a => a.expand(db, "People", p => p)) 443 | ``` 444 | 445 | ```json 446 | [ 447 | { 448 | "Name": "Rotterdam", 449 | "Population": 1250000, 450 | "Addresses": [ 451 | { 452 | "Street": "Afrikaanderplein", 453 | "Number": 7, 454 | "Postcode": "3072 EA", 455 | "People": [ 456 | { 457 | "Name": "John", 458 | "Surname": "Doe", 459 | "Age": 27 460 | }, 461 | { 462 | "Name": "Jane", 463 | "Surname": "Doe", 464 | "Age": 31 465 | } 466 | ] 467 | } 468 | ] 469 | }, 470 | ... 471 | ] 472 | ``` 473 | 474 | Sometimes, we need to rename fields. Sometimes, we need to rename relations so that the generated result type fits our needs. 475 | For each city, expand its addresses, and for each address, expand the people living there. Rename the `People` attribute of `Address` to `Inhabitants`\: 476 | 477 | ```ts 478 | const q4 = db.from("Cities").expand(db, "Addresses", a => a.expandAs(db, "People", "Inhabitants", p => p)) 479 | ``` 480 | 481 | Now the `People` found inside `Addresses` are called `Inhabitants` in the result\: 482 | 483 | ```json 484 | [ 485 | { 486 | "Name": "Rotterdam", 487 | "Population": 1250000, 488 | "Addresses": [ 489 | { 490 | "Street": "Afrikaanderplein", 491 | "Number": 7, 492 | "Postcode": "3072 EA", 493 | "Inhabitants": [ 494 | { 495 | "Name": "John", 496 | "Surname": "Doe", 497 | "Age": 27 498 | }, 499 | { 500 | "Name": "Jane", 501 | "Surname": "Doe", 502 | "Age": 31 503 | } 504 | ] 505 | } 506 | ] 507 | }, 508 | ... 509 | ] 510 | ``` 511 | 512 | We can mix and match operators at any level of nesting, thanks to proper composition of our query operators. Also `join` and `expand` respect this constraint. 513 | 514 | For each person, and their address, make a single entity with all the attributes of the two input entities (we selected no attributes from the addresses though, so we only get the attributes of each `Person` in practice). For each resulting entity, expand the city found at that address\: 515 | 516 | ```ts 517 | const q5 = db.from("People").join(db, "Addresses", a => a.select().expand(db, "Cities", c => c)) 518 | ``` 519 | 520 | This way, we get `People` and their `Cities`, and it looks like we skipped the `Addresses` entity that was laying in the middle (actually, we just skip merging the attributes of `Address` into `Person`, but the result is the same)\: 521 | 522 | ```json 523 | [ 524 | { 525 | "Name": "John", 526 | "Surname": "Doe", 527 | "Age": 27, 528 | "Cities": [ 529 | { 530 | "Name": "Rotterdam", 531 | "Population": 1250000 532 | } 533 | ] 534 | }, 535 | ... 536 | { 537 | "Name": "Giuseppe", 538 | "Surname": "Rossi", 539 | "Age": 35, 540 | "Cities": [ 541 | { 542 | "Name": "Amsterdam", 543 | "Population": 1250000 544 | } 545 | ] 546 | }, 547 | ... 548 | ] 549 | ``` 550 | 551 | Just as expected. 552 | 553 | 554 | # Conclusion 555 | In order to facilitate complex client\-side data processing tasks, I decided to build a client\-side declarative data processing library that combines the advantages of relational databases, graph API's, and the fantastic type safety of TypeScript. 556 | 557 | Thanks to this library, [which can be found on `npm`](https://www.npmjs.com/package/ts-in-memory-database), you can process data quickly and easily, with enhanced productivity and less bugs. 558 | 559 | 560 | Thank you for coming all the way to the end, I hope you enjoyed reading this article as much as I enjoyed writing it ;) 561 | 562 | 563 | # Appendix: about the author 564 | Hi! I am Giuseppe Maggiore. I have an academic background (PhD) in Computer Science, specifically compilers and functional programming (not so surprising eh...). I am now CTO of [Hoppinger](https://www.hoppinger.com/), a wonderful software development company in the heart of Rotterdam (Netherlands). 565 | 566 | I am always looking for talented software engineers who get excited at the thought of type safety, reliable software, functional programming, and so on. If that is the case, do get in touch with us, [we always have open positions for smart people](https://www.hoppinger.com/vacatures/)! 567 | 568 | 569 | # Appendix: about the internals of the library 570 | This bit is optional, and reserved for the brave of heart :) 571 | 572 | The core of the library resides in the `Entity` type, which takes as input all the type\-level information needed to track an entity\: its primary key (`Id`), its fields, its relations, and the whole context (`Db`)\: 573 | 574 | ```ts 575 | interface Entity { 576 | ... 577 | } 578 | ``` 579 | 580 | Let's take an operator such as `select`. `select` takes as input a series of `keys`, which have as type `k`. `k` must be a subset of the attributes of the fields, that is `k extends keyof Fields`. The entity that results from a selection, is in essence the same as the original, but the fields are only the ones selected. In order to specify that the fields of the entity are limited to those specified in `k`, we can use the generic `Pick` type by stating `Pick`\: 581 | 582 | ```ts 583 | select: (...keys: k[]) => Entity, Relations, Db> 584 | ``` 585 | 586 | Expanding a relation is a bit more complex. Of course, we must specify the type `r` that is one of the available relations (thus `r extends keyof Relations`). The result will add an extra field to the ones already selected (`Fields & { [x in TargetName]: Map }`), and in this field we put whatever came out of processing the relation through a specified lambda, `q`\: 587 | 588 | ```ts 589 | expand: (db: Database, relation: r, q: Fun, Entity>) => 590 | Entity]: Map }, Without, Db> 591 | ``` 592 | 593 | If you go thorugh the [core of the library](../database.ts), you will notice that we make heavy use of generics in order to track the structure of a query context as the query is defined by a developer, and that we use TypeScript's advanced types such as `&`, `keyof`, and `[*]` in order to establish constraints and procedurally build up the type of the final result. 594 | 595 | This is not the first time I applied these principles to a library in TypeScript. For example, together with brilliant Wim Jongeneel, we have built [a typesafe OData client](https://www.npmjs.com/package/typescript-odata-client) that uses the very same principles and philosophy as this library. Stay tuned, because in the coming months we will merge the two, so that our `ts-odata-client` will be able to output an in\-memory relational database out of the box for further processing! 596 | -------------------------------------------------------------------------------- /Article/pics/FieldIntelliSense.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hoppinger/TypeScript-typesafe-relational-processor/d6bf8c4385004a62ea19e6a93553b0507942c3f7/Article/pics/FieldIntelliSense.png -------------------------------------------------------------------------------- /Article/pics/FieldValidation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hoppinger/TypeScript-typesafe-relational-processor/d6bf8c4385004a62ea19e6a93553b0507942c3f7/Article/pics/FieldValidation.png -------------------------------------------------------------------------------- /Article/pics/LambdaIntellisense.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hoppinger/TypeScript-typesafe-relational-processor/d6bf8c4385004a62ea19e6a93553b0507942c3f7/Article/pics/LambdaIntellisense.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Typesafe in-memory database 2 | 3 | This project features an in\-memory, typesafe database that you can use to quickly process complex data structures. 4 | 5 | Typesafety ensures that structural errors are either hard or impossible to make. 6 | 7 | A fluent syntax helps discover the various operators. 8 | 9 | ## Getting started 10 | Download the package from `npm` with\: 11 | 12 | ``` 13 | npm install ts-in-memory-database 14 | ``` 15 | 16 | Import the package at the top of your TypeScript file\: 17 | 18 | ```ts 19 | import * as TSInMemDb from "ts-in-memory-database" 20 | ``` 21 | 22 | Define the datatypes of your database\: 23 | 24 | ```ts 25 | interface Person { 26 | Name: string 27 | Surname: string 28 | Age: number 29 | } 30 | 31 | interface Address { 32 | Street: string 33 | Number: number 34 | Postcode: string 35 | } 36 | 37 | interface City { 38 | Name: string 39 | Population: number 40 | } 41 | ``` 42 | 43 | Define the entities and relations of your database\: 44 | 45 | ```ts 46 | interface MyEntities { 47 | People: Entity<{ PersonId: number }, Person, { 48 | Addresses: Relation 49 | }, MyEntities> 50 | Addresses: Entity<{ AddressId: number }, Address, { 51 | Cities: Relation, 52 | People: Inverted["Addresses"]> 53 | }, MyEntities> 54 | Cities: Entity<{ CityId: number }, City, { 55 | Addresses: Inverted["Cities"]> 56 | }, MyEntities> 57 | } 58 | ``` 59 | 60 | Create a database. You need to specify both entities and relationships. You could do this manually. It is a bit verbose, so check out the [full sample](./sample1.ts). When you have all the data, you can instantiate `MyEntities`, and with it a database around it\: 61 | 62 | ```ts 63 | const myEntities: MyEntities = { 64 | People: ..., 65 | Addresses: ..., 66 | Cities: ... 67 | } 68 | 69 | const db: Database = Database(myEntities) 70 | ``` 71 | 72 | Finally, we can run some queries\: 73 | 74 | Get all people whose `Name` starts with `Giu`. Rename attribute `Name` to `FirstName` in the result\: 75 | 76 | ```ts 77 | const q0 = db.from("People").fieldAs("Name", "FirstName").select("FirstName").filter(p => p.FirstName.startsWith("Giu")) 78 | 79 | /* Returns 80 | [ 81 | { 82 | "FirstName": "Giuseppe" 83 | }, 84 | { 85 | "FirstName": "Giulia" 86 | } 87 | ] 88 | */ 89 | ``` 90 | 91 | For each person, get their addresses as well. Select only the `Street` and `Number` attributes of each address\: 92 | 93 | ```ts 94 | const q1 = db.from("People").expand(db, "Addresses", a => a.select("Street", "Number")) 95 | 96 | /* Returns 97 | [ 98 | { 99 | "Name": "John", 100 | "Surname": "Doe", 101 | "Age": 27, 102 | "Addresses": [ 103 | { 104 | "Street": "Afrikaanderplein", 105 | "Number": 7 106 | } 107 | ] 108 | }, 109 | ... 110 | { 111 | "Name": "Giuseppe", 112 | "Surname": "Rossi", 113 | "Age": 35, 114 | "Addresses": [ 115 | { 116 | "Street": "Kalverstraat", 117 | "Number": 92 118 | } 119 | ] 120 | }, 121 | ... 122 | ] 123 | */ 124 | ``` 125 | 126 | For each person, their address, and its city, make a single entity with all the attributes of the three input entities. Select only the `Street` and `Number` attributes of each address. Rename the `Name` attribute of the `City` to `CityName` to avoid overlap with `Person` `Name`\: 127 | 128 | ```ts 129 | const q2 = db.from("People").join(db, "Addresses", a => a.select("Street", "Number").join(db, "Cities", c => c.fieldAs("Name", "CityName"))) 130 | 131 | /* Returns 132 | [ 133 | { 134 | "Name": "John", 135 | "Surname": "Doe", 136 | "Age": 27, 137 | "Street": "Afrikaanderplein", 138 | "Number": 7, 139 | "Population": 1250000, 140 | "CityName": "Rotterdam" 141 | }, 142 | ... 143 | { 144 | "Name": "Giuseppe", 145 | "Surname": "Rossi", 146 | "Age": 35, 147 | "Street": "Kalverstraat", 148 | "Number": 92, 149 | "Population": 1250000, 150 | "CityName": "Amsterdam" 151 | }, 152 | ... 153 | ] 154 | */ 155 | ``` 156 | 157 | For each city, expand its addresses, and for each address, expand the people living there\: 158 | 159 | ```ts 160 | const q3 = db.from("Cities").expand(db, "Addresses", a => a.expand(db, "People", p => p)) 161 | 162 | /* Returns 163 | [ 164 | { 165 | "Name": "Rotterdam", 166 | "Population": 1250000, 167 | "Addresses": [ 168 | { 169 | "Street": "Afrikaanderplein", 170 | "Number": 7, 171 | "Postcode": "3072 EA", 172 | "People": [ 173 | { 174 | "Name": "John", 175 | "Surname": "Doe", 176 | "Age": 27 177 | }, 178 | { 179 | "Name": "Jane", 180 | "Surname": "Doe", 181 | "Age": 31 182 | } 183 | ] 184 | } 185 | ] 186 | }, 187 | ... 188 | ] 189 | */ 190 | ``` 191 | 192 | For each city, expand its addresses, and for each address, expand the people living there. Rename the `People` attribute of `Address` to `Inhabitants`\: 193 | 194 | ```ts 195 | const q4 = db.from("Cities").expand(db, "Addresses", a => a.expandAs(db, "People", "Inhabitants", p => p)) 196 | 197 | /* Returns 198 | [ 199 | { 200 | "Name": "Rotterdam", 201 | "Population": 1250000, 202 | "Addresses": [ 203 | { 204 | "Street": "Afrikaanderplein", 205 | "Number": 7, 206 | "Postcode": "3072 EA", 207 | "Inhabitants": [ 208 | { 209 | "Name": "John", 210 | "Surname": "Doe", 211 | "Age": 27 212 | }, 213 | { 214 | "Name": "Jane", 215 | "Surname": "Doe", 216 | "Age": 31 217 | } 218 | ] 219 | } 220 | ] 221 | }, 222 | ... 223 | ] 224 | */ 225 | ``` 226 | 227 | For each person, and their address, make a single entity with all the attributes of the two input entities (we selected no attributes from the addresses though, so we only get the attributes of each `Person` in practice). For each resulting entity, expand the city found at that address\: 228 | 229 | ```ts 230 | const q5 = db.from("People").join(db, "Addresses", a => a.select().expand(db, "Cities", c => c)) 231 | 232 | /* Returns 233 | [ 234 | { 235 | "Name": "John", 236 | "Surname": "Doe", 237 | "Age": 27, 238 | "Cities": [ 239 | { 240 | "Name": "Rotterdam", 241 | "Population": 1250000 242 | } 243 | ] 244 | }, 245 | ... 246 | { 247 | "Name": "Giuseppe", 248 | "Surname": "Rossi", 249 | "Age": 35, 250 | "Cities": [ 251 | { 252 | "Name": "Amsterdam", 253 | "Population": 1250000 254 | } 255 | ] 256 | }, 257 | ... 258 | ] 259 | */ 260 | ``` 261 | 262 | 263 | ## Still missing 264 | Some SQL\-style operators such as `GroupBy` are still missing. The only supported `join` is actually an `inner join`. 265 | 266 | Writing to the database can already be done, but is not particularly ergonomic. For now the focus lies on data processing\: if needed, some handier writing operators could be added. 267 | 268 | We want to build some operators to import results from, say, a _Graph Api_ result such as OData or GraphQL into our database structure. This way a developer would be able to quickly fill up their local database 269 | -------------------------------------------------------------------------------- /Typesafe in-memory database.njsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 14.0 4 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) 5 | Typesafe in-memory database 6 | Hierarchy processor 7 | True 8 | 9 | 10 | 11 | Debug 12 | 2.0 13 | 9f7f5fee-7107-4249-85a7-022bc39c499f 14 | 15 | 16 | ./out/app.js 17 | False 18 | 19 | 20 | . 21 | . 22 | v4.0 23 | {3AF33F2E-1136-4D97-BBB7-1795711AC8B8};{9092AA53-FB77-4645-B42D-1CCCA6BD08BD} 24 | true 25 | False 26 | 27 | 28 | true 29 | 30 | 31 | true 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | Code 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | False 53 | True 54 | 0 55 | / 56 | http://localhost:48022/ 57 | False 58 | True 59 | http://localhost:1337 60 | False 61 | 62 | 63 | 64 | 65 | 66 | 67 | CurrentPage 68 | True 69 | False 70 | False 71 | False 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | False 81 | False 82 | 83 | 84 | 85 | 86 | -------------------------------------------------------------------------------- /Typesafe in-memory database.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.29519.87 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9092AA53-FB77-4645-B42D-1CCCA6BD08BD}") = "Typesafe in-memory database", "Typesafe in-memory database.njsproj", "{9F7F5FEE-7107-4249-85A7-022BC39C499F}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {9F7F5FEE-7107-4249-85A7-022BC39C499F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {9F7F5FEE-7107-4249-85A7-022BC39C499F}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {9F7F5FEE-7107-4249-85A7-022BC39C499F}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {9F7F5FEE-7107-4249-85A7-022BC39C499F}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | GlobalSection(ExtensibilityGlobals) = postSolution 23 | SolutionGuid = {D8622073-F7A4-40D1-8F0C-78437B9E9C81} 24 | EndGlobalSection 25 | EndGlobal 26 | -------------------------------------------------------------------------------- /app.ts: -------------------------------------------------------------------------------- 1 | export * from "./utils" 2 | export * from "./database" 3 | 4 | import { test } from "./sample" 5 | test() 6 | 7 | /* todo: 8 | * article 9 | * add link to github repo in Readme 10 | * odataParser to create a database (Wim) 11 | * define better constraints on Relations in Entity and expand, expandAs, and join? (Wim/Francesco) 12 | */ 13 | -------------------------------------------------------------------------------- /database.ts: -------------------------------------------------------------------------------- 1 | import { Record, List, Map, Set, is } from "immutable" 2 | import { Without, Fun, Rename } from "./utils" 3 | 4 | export type EntityId = 5 | E extends Entity ? 6 | Id : never 7 | 8 | export type EntityRelations = 9 | E extends Entity ? 10 | Relations : never 11 | 12 | export type InvertedAriety = 13 | Ariety extends "1-N" ? "N-1" : Ariety extends "N-1" ? "1-N" : Ariety 14 | 15 | export type Inverted = 16 | R extends Relation ? 17 | Relation> 18 | : never 19 | 20 | export type SourceName = 21 | R extends Relation ? 22 | SourceName : never 23 | 24 | export type TargetName = 25 | R extends Relation ? 26 | TargetName : never 27 | 28 | export type Source = 29 | R extends Relation ? 30 | Db[SourceName] : never 31 | 32 | export type Target = 33 | R extends Relation ? 34 | Db[TargetName] : never 35 | 36 | export interface Relation { 37 | Inverted: () => Inverted> 38 | values: () => Map>, Set>>> 39 | } 40 | 41 | export interface Entity { 42 | fieldAs: (key: k, keyNew:kNew) => Entity & { [x in kNew]:Fields[k] }, Relations, Db> 43 | select: (...keys: k[]) => Entity, Relations, Db> 44 | filter: (p: (fields:Fields, id:Record) => boolean) => Entity 45 | expand: (db: Database, relation: r, q: Fun, Entity>) => 46 | Entity]: Map }, Without, Db> 47 | expandAs: (db: Database, relation: r, as:as, q: Fun, Entity>) => 48 | Entity }, Without, Db> 49 | join: (db: Database, relation: r, q: Fun, Entity>) => 50 | Entity 51 | relations: () => Relations 52 | values: () => Map, Fields> 53 | } 54 | 55 | export interface Database { 56 | from: (entity: E) => Entities[E] 57 | } 58 | 59 | const MakePair = (t: t, u: u): [t, u] => [t, u] 60 | 61 | export const Relation = (values: Map < Record < EntityId < Db[SourceName] >>, Set >>>): Relation => ({ 62 | Inverted: (): Inverted> => { 63 | let invertedValues = Map>, Set>>>() 64 | const emptySet = Set() 65 | values.forEach((k2s, k1) => 66 | k2s.forEach(k2 => 67 | invertedValues = invertedValues.update(k2, emptySet, x => x.add(k1)) 68 | ) 69 | ) 70 | return Relation>(invertedValues) 71 | }, 72 | values: () => values 73 | }) 74 | 75 | export const Entity = () => (values:Map, Fields>, relations: Relations): Entity => ({ 76 | fieldAs: (key: k, keyNew: kNew): 77 | Entity & { [x in kNew]: Fields[k] }, Relations, Db> => 78 | Entity()(values.map(f => Rename(key, keyNew, f)), relations), 79 | select: (...keys: k[]): Entity, Relations, Db> => 80 | Entity()(values.map(f => keys.reduce((a, e) => ({ ...a, [e]: f[e] }), {}) as Pick), relations), 81 | filter: (p: (fields: Fields, id: Record) => boolean): Entity => 82 | Entity()(values.filter(p), relations), 83 | expand: function (this, db: Database, relation: r, q: Fun, Entity>): 84 | Entity]: Map }, Without, Db> { 85 | return this.expandAs(db, relation, relation, q) 86 | }, 87 | expandAs: (db: Database, relation: r, as: as, q: Fun, Entity>) : 88 | Entity }, Without, Db> => { 89 | const allLinks = (relations[relation] as any).values() 90 | const otherEntity = (db.from(relation as any) as any) 91 | const allTargets = otherEntity.values() 92 | const res = Entity()(values.map((fields, id) => { 93 | const links = allLinks.get(id) || Set() 94 | const targets = links.reduce((acc: any, targetId: any) => acc.set(targetId, allTargets.get(targetId)), Map()) 95 | return { 96 | ...fields, 97 | ...Rename(relation, as, 98 | { [relation]: q(Entity()(targets, otherEntity.relations()) as any).values() as any }) 99 | } 100 | }), Without(relation, relations)) 101 | return res 102 | }, 103 | join: (db: Database, relation: r, q: Fun, Entity>) : 104 | Entity => { 105 | const allLinks = (relations[relation] as any).values() 106 | const otherEntity = (db.from(relation as any) as any) 107 | const allTargets = otherEntity.values() 108 | let mergedValues = Map, Fields & Fields2>() 109 | values.forEach((sourceFields, sourceId) => { 110 | const links = allLinks.get(sourceId) || Set() 111 | const targets = links.reduce((acc: any, targetId: any) => acc.set(targetId, allTargets.get(targetId)), Map()) 112 | q(Entity()(targets, otherEntity.relations()) as any).values().forEach((targetFields: any, targetId: any) => { 113 | mergedValues = mergedValues.set(sourceId, 114 | { ...sourceFields, ...targetFields }) 115 | }) 116 | }) 117 | const res = Entity()(mergedValues, relations) 118 | return res 119 | }, 120 | relations: (): Relations => relations, 121 | values: () : Map, Fields> => values 122 | }) 123 | 124 | export const Database = (entities: Entities): Database => ({ 125 | from: (entity: E): Entities[E] => entities[entity] 126 | }) 127 | -------------------------------------------------------------------------------- /flattener.ts.notyet: -------------------------------------------------------------------------------- 1 | //type Primitives = { string: string, number: number, boolean: boolean } 2 | 3 | //type Flattener = { 4 | // withField: (attribute: a, type: t) => 5 | // Flattener 6 | // withMany: (relation: r, 7 | // builder: Fun, Flattener>) => 8 | // Flattener 11 | // withOne: (relation: r, 12 | // builder: Fun, Flattener>) => 13 | // Flattener 16 | // run: () => flatResult & { [x in currentEntity]: currentFields[] } & { relations: relations } 17 | //} 18 | 19 | ///* 20 | // * relations in type 21 | // * multiple relations from same source in type 22 | // * self-references 23 | // * runtime (flattener) 24 | // * re-hierarchify types 25 | // * runtime (re-hierarchifier) 26 | // */ 27 | 28 | //let f: Flattener<"person", {}, {}, {}> = null! 29 | 30 | //const result = f 31 | // .withField("name", "string") 32 | // .withField("surname", "string") 33 | // .withField("age", "number") 34 | // .withOne("city", b => b 35 | // .withField("name", "string") 36 | // .withField("population", "number") 37 | // .withOne("country", b => 38 | // b.withField("name", "string") 39 | // .withField("continent", "string") 40 | // ) 41 | // ) 42 | // .withMany("employer", b => b 43 | // .withField("name", "string") 44 | // .withField("employees", "number") 45 | // ) 46 | // .run() 47 | 48 | //const x: typeof result = null! 49 | -------------------------------------------------------------------------------- /out/app.d.ts: -------------------------------------------------------------------------------- 1 | export * from "./utils"; 2 | export * from "./database"; 3 | -------------------------------------------------------------------------------- /out/app.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | function __export(m) { 3 | for (var p in m) if (!exports.hasOwnProperty(p)) exports[p] = m[p]; 4 | } 5 | Object.defineProperty(exports, "__esModule", { value: true }); 6 | __export(require("./utils")); 7 | __export(require("./database")); 8 | const sample_1 = require("./sample"); 9 | sample_1.test(); 10 | /* todo: 11 | * article 12 | * odataParser to create a database (Wim) 13 | * define better constraints on Relations in Entity and expand, expandAs, and join? (Wim/Francesco) 14 | */ 15 | //# sourceMappingURL=app.js.map -------------------------------------------------------------------------------- /out/app.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"app.js","sourceRoot":"","sources":["../app.ts"],"names":[],"mappings":";;;;;AAAA,6BAAuB;AACvB,gCAA0B;AAE1B,qCAA+B;AAC/B,aAAI,EAAE,CAAA;AAEN;;;;GAIG"} -------------------------------------------------------------------------------- /out/database.d.ts: -------------------------------------------------------------------------------- 1 | import { Record, Map, Set } from "immutable"; 2 | import { Without, Fun } from "./utils"; 3 | export declare type EntityId = E extends Entity ? Id : never; 4 | export declare type EntityRelations = E extends Entity ? Relations : never; 5 | export declare type InvertedAriety = Ariety extends "1-N" ? "N-1" : Ariety extends "N-1" ? "1-N" : Ariety; 6 | export declare type Inverted = R extends Relation ? Relation> : never; 7 | export declare type SourceName = R extends Relation ? SourceName : never; 8 | export declare type TargetName = R extends Relation ? TargetName : never; 9 | export declare type Source = R extends Relation ? Db[SourceName] : never; 10 | export declare type Target = R extends Relation ? Db[TargetName] : never; 11 | export interface Relation { 12 | Inverted: () => Inverted>; 13 | values: () => Map>, Set>>>; 14 | } 15 | export interface Entity { 16 | fieldAs: (key: k, keyNew: kNew) => Entity & { 17 | [x in kNew]: Fields[k]; 18 | }, Relations, Db>; 19 | select: (...keys: k[]) => Entity, Relations, Db>; 20 | filter: (p: (fields: Fields, id: Record) => boolean) => Entity; 21 | expand: (db: Database, relation: r, q: Fun, Entity>) => Entity]: Map; 23 | }, Without, Db>; 24 | expandAs: (db: Database, relation: r, as: as, q: Fun, Entity>) => Entity; 26 | }, Without, Db>; 27 | join: (db: Database, relation: r, q: Fun, Entity>) => Entity; 28 | relations: () => Relations; 29 | values: () => Map, Fields>; 30 | } 31 | export interface Database { 32 | from: (entity: E) => Entities[E]; 33 | } 34 | export declare const Relation: (values: Map>, Set>>>) => Relation; 35 | export declare const Entity: () => (values: Map, Fields>, relations: Relations) => Entity; 36 | export declare const Database: (entities: Entities) => Database; 37 | -------------------------------------------------------------------------------- /out/database.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | const immutable_1 = require("immutable"); 4 | const utils_1 = require("./utils"); 5 | const MakePair = (t, u) => [t, u]; 6 | exports.Relation = (values) => ({ 7 | Inverted: () => { 8 | let invertedValues = immutable_1.Map(); 9 | const emptySet = immutable_1.Set(); 10 | values.forEach((k2s, k1) => k2s.forEach(k2 => invertedValues = invertedValues.update(k2, emptySet, x => x.add(k1)))); 11 | return exports.Relation(invertedValues); 12 | }, 13 | values: () => values 14 | }); 15 | exports.Entity = () => (values, relations) => ({ 16 | fieldAs: (key, keyNew) => exports.Entity()(values.map(f => utils_1.Rename(key, keyNew, f)), relations), 17 | select: (...keys) => exports.Entity()(values.map(f => keys.reduce((a, e) => (Object.assign(Object.assign({}, a), { [e]: f[e] })), {})), relations), 18 | filter: (p) => exports.Entity()(values.filter(p), relations), 19 | expand: function (db, relation, q) { 20 | return this.expandAs(db, relation, relation, q); 21 | }, 22 | expandAs: (db, relation, as, q) => { 23 | const allLinks = relations[relation].values(); 24 | const otherEntity = db.from(relation); 25 | const allTargets = otherEntity.values(); 26 | const res = exports.Entity()(values.map((fields, id) => { 27 | const links = allLinks.get(id) || immutable_1.Set(); 28 | const targets = links.reduce((acc, targetId) => acc.set(targetId, allTargets.get(targetId)), immutable_1.Map()); 29 | return Object.assign(Object.assign({}, fields), utils_1.Rename(relation, as, { [relation]: q(exports.Entity()(targets, otherEntity.relations())).values() })); 30 | }), utils_1.Without(relation, relations)); 31 | return res; 32 | }, 33 | join: (db, relation, q) => { 34 | const allLinks = relations[relation].values(); 35 | const otherEntity = db.from(relation); 36 | const allTargets = otherEntity.values(); 37 | let mergedValues = immutable_1.Map(); 38 | values.forEach((sourceFields, sourceId) => { 39 | const links = allLinks.get(sourceId) || immutable_1.Set(); 40 | const targets = links.reduce((acc, targetId) => acc.set(targetId, allTargets.get(targetId)), immutable_1.Map()); 41 | q(exports.Entity()(targets, otherEntity.relations())).values().forEach((targetFields, targetId) => { 42 | mergedValues = mergedValues.set(sourceId, Object.assign(Object.assign({}, sourceFields), targetFields)); 43 | }); 44 | }); 45 | const res = exports.Entity()(mergedValues, relations); 46 | return res; 47 | }, 48 | relations: () => relations, 49 | values: () => values 50 | }); 51 | exports.Database = (entities) => ({ 52 | from: (entity) => entities[entity] 53 | }); 54 | //# sourceMappingURL=database.js.map -------------------------------------------------------------------------------- /out/database.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"database.js","sourceRoot":"","sources":["../database.ts"],"names":[],"mappings":"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"} -------------------------------------------------------------------------------- /out/sample.d.ts: -------------------------------------------------------------------------------- 1 | export declare const test: () => void; 2 | -------------------------------------------------------------------------------- /out/sample.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | const immutable_1 = require("immutable"); 4 | const database_1 = require("./database"); 5 | const PersonId = immutable_1.Record({ PersonId: 0 }); 6 | const AddressId = immutable_1.Record({ AddressId: 0 }); 7 | const CityId = immutable_1.Record({ CityId: 0 }); 8 | const personAddresses = database_1.Relation(immutable_1.Map() 9 | .set(PersonId({ PersonId: 0 }), immutable_1.Set() 10 | .add(AddressId({ AddressId: 0 }))) 11 | .set(PersonId({ PersonId: 1 }), immutable_1.Set() 12 | .add(AddressId({ AddressId: 0 }))) 13 | .set(PersonId({ PersonId: 2 }), immutable_1.Set() 14 | .add(AddressId({ AddressId: 1 }))) 15 | .set(PersonId({ PersonId: 3 }), immutable_1.Set() 16 | .add(AddressId({ AddressId: 1 }))) 17 | .set(PersonId({ PersonId: 4 }), immutable_1.Set() 18 | .add(AddressId({ AddressId: 1 }))) 19 | .set(PersonId({ PersonId: 5 }), immutable_1.Set() 20 | .add(AddressId({ AddressId: 1 }))) 21 | .set(PersonId({ PersonId: 6 }), immutable_1.Set() 22 | .add(AddressId({ AddressId: 2 })))); 23 | const addressCities = database_1.Relation(immutable_1.Map() 24 | .set(AddressId({ AddressId: 0 }), immutable_1.Set() 25 | .add(CityId({ CityId: 0 }))) 26 | .set(AddressId({ AddressId: 1 }), immutable_1.Set() 27 | .add(CityId({ CityId: 1 }))) 28 | .set(AddressId({ AddressId: 2 }), immutable_1.Set() 29 | .add(CityId({ CityId: 2 })))); 30 | const myEntities = { 31 | People: database_1.Entity()(immutable_1.Map() 32 | .set(PersonId({ PersonId: 0 }), { Name: "John", Surname: "Doe", Age: 27 }) 33 | .set(PersonId({ PersonId: 1 }), { Name: "Jane", Surname: "Doe", Age: 31 }) 34 | .set(PersonId({ PersonId: 2 }), { Name: "Giuseppe", Surname: "Rossi", Age: 35 }) 35 | .set(PersonId({ PersonId: 3 }), { Name: "Giulia", Surname: "Verdi", Age: 34 }) 36 | .set(PersonId({ PersonId: 4 }), { Name: "Reby", Surname: "Rossi", Age: 7 }) 37 | .set(PersonId({ PersonId: 5 }), { Name: "Rechy", Surname: "Rossi", Age: 5 }) 38 | .set(PersonId({ PersonId: 6 }), { Name: "Francis", Surname: "Dee Jay", Age: 35 }), { 39 | Addresses: personAddresses 40 | }), 41 | Addresses: database_1.Entity()(immutable_1.Map() 42 | .set(AddressId({ AddressId: 0 }), { Street: "Afrikaanderplein", Number: 7, Postcode: "3072 EA" }) 43 | .set(AddressId({ AddressId: 1 }), { Street: "Kalverstraat", Number: 92, Postcode: "1012 PH" }) 44 | .set(AddressId({ AddressId: 2 }), { Street: "Heidelaan", Number: 139, Postcode: "3851 EX" }), { 45 | People: personAddresses.Inverted(), 46 | Cities: addressCities, 47 | }), 48 | // sort of random values, did not check if they are accurate :) 49 | Cities: database_1.Entity()(immutable_1.Map() 50 | .set(CityId({ CityId: 0 }), { Name: "Rotterdam", Population: 1250000 }) 51 | .set(CityId({ CityId: 1 }), { Name: "Amsterdam", Population: 1250000 }) 52 | .set(CityId({ CityId: 2 }), { Name: "Hengelo", Population: 150000 }), { 53 | Addresses: addressCities.Inverted() 54 | }) 55 | }; 56 | exports.test = () => { 57 | const db = database_1.Database(myEntities); 58 | const q0 = db.from("People").fieldAs("Name", "FirstName").select("FirstName").filter(p => p.FirstName.startsWith("Giu")); 59 | const q1 = db.from("People").expand(db, "Addresses", a => a.select("Street", "Number")); 60 | const q2 = db.from("People").join(db, "Addresses", a => a.select("Street", "Number").join(db, "Cities", c => c.fieldAs("Name", "CityName"))); 61 | const q3 = db.from("Cities").expand(db, "Addresses", a => a.expand(db, "People", p => p)); 62 | const q4 = db.from("Cities").expand(db, "Addresses", a => a.expandAs(db, "People", "Inhabitants", p => p)); 63 | const q5 = db.from("People").join(db, "Addresses", a => a.select().expand(db, "Cities", c => c)); 64 | const v0 = q0.values().toArray().map(x => x[1]); 65 | const v1 = q1.values().toArray().map(x => (Object.assign(Object.assign({}, x[1]), { Addresses: x[1].Addresses.toArray().map(a => a[1]) }))); 66 | const v2 = q2.values().toArray().map(x => x[1]); 67 | const v3 = q3.values().toArray().map(x => (Object.assign(Object.assign({}, x[1]), { Addresses: x[1].Addresses.toArray().map(a => (Object.assign(Object.assign({}, a[1]), { People: a[1].People.toArray().map(p => p[1]) }))) }))); 68 | const v4 = q4.values().toArray().map(x => (Object.assign(Object.assign({}, x[1]), { Addresses: x[1].Addresses.toArray().map(a => (Object.assign(Object.assign({}, a[1]), { Inhabitants: a[1].Inhabitants.toArray().map(p => p[1]) }))) }))); 69 | const v5 = q5.values().toArray().map(x => (Object.assign(Object.assign({}, x[1]), { Cities: x[1].Cities.toArray().map(c => c[1]) }))); 70 | const results = [v0, v1, v2, v3, v4, v5].map(v => JSON.stringify(v, null, 1)); 71 | results.forEach(r => console.log(r)); 72 | console.log(`Done.`); 73 | }; 74 | //# sourceMappingURL=sample.js.map -------------------------------------------------------------------------------- /out/sample.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"sample.js","sourceRoot":"","sources":["../sample.ts"],"names":[],"mappings":"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"} -------------------------------------------------------------------------------- /out/tsconfig.tsbuildinfo: -------------------------------------------------------------------------------- 1 | { 2 | "program": { 3 | "fileInfos": { 4 | "../node_modules/typescript/lib/lib.es5.d.ts": { 5 | "version": "b42eddba1a53c9d27279cfe7fc0416c10a81489826ad47e39013b9d340fc0cc7", 6 | "signature": "b42eddba1a53c9d27279cfe7fc0416c10a81489826ad47e39013b9d340fc0cc7" 7 | }, 8 | "../node_modules/typescript/lib/lib.es2015.d.ts": { 9 | "version": "7994d44005046d1413ea31d046577cdda33b8b2470f30281fd9c8b3c99fe2d96", 10 | "signature": "7994d44005046d1413ea31d046577cdda33b8b2470f30281fd9c8b3c99fe2d96" 11 | }, 12 | "../node_modules/typescript/lib/lib.es2016.d.ts": { 13 | "version": "5f217838d25704474d9ef93774f04164889169ca31475fe423a9de6758f058d1", 14 | "signature": "5f217838d25704474d9ef93774f04164889169ca31475fe423a9de6758f058d1" 15 | }, 16 | "../node_modules/typescript/lib/lib.es2017.d.ts": { 17 | "version": "459097c7bdd88fc5731367e56591e4f465f2c9de81a35427a7bd473165c34743", 18 | "signature": "459097c7bdd88fc5731367e56591e4f465f2c9de81a35427a7bd473165c34743" 19 | }, 20 | "../node_modules/typescript/lib/lib.es2015.core.d.ts": { 21 | "version": "734ddc145e147fbcd55f07d034f50ccff1086f5a880107665ec326fb368876f6", 22 | "signature": "734ddc145e147fbcd55f07d034f50ccff1086f5a880107665ec326fb368876f6" 23 | }, 24 | "../node_modules/typescript/lib/lib.es2015.collection.d.ts": { 25 | "version": "4a0862a21f4700de873db3b916f70e41570e2f558da77d2087c9490f5a0615d8", 26 | "signature": "4a0862a21f4700de873db3b916f70e41570e2f558da77d2087c9490f5a0615d8" 27 | }, 28 | "../node_modules/typescript/lib/lib.es2015.generator.d.ts": { 29 | "version": "765e0e9c9d74cf4d031ca8b0bdb269a853e7d81eda6354c8510218d03db12122", 30 | "signature": "765e0e9c9d74cf4d031ca8b0bdb269a853e7d81eda6354c8510218d03db12122" 31 | }, 32 | "../node_modules/typescript/lib/lib.es2015.iterable.d.ts": { 33 | "version": "285958e7699f1babd76d595830207f18d719662a0c30fac7baca7df7162a9210", 34 | "signature": "285958e7699f1babd76d595830207f18d719662a0c30fac7baca7df7162a9210" 35 | }, 36 | "../node_modules/typescript/lib/lib.es2015.promise.d.ts": { 37 | "version": "d4deaafbb18680e3143e8b471acd650ed6f72a408a33137f0a0dd104fbe7f8ca", 38 | "signature": "d4deaafbb18680e3143e8b471acd650ed6f72a408a33137f0a0dd104fbe7f8ca" 39 | }, 40 | "../node_modules/typescript/lib/lib.es2015.proxy.d.ts": { 41 | "version": "5e72f949a89717db444e3bd9433468890068bb21a5638d8ab15a1359e05e54fe", 42 | "signature": "5e72f949a89717db444e3bd9433468890068bb21a5638d8ab15a1359e05e54fe" 43 | }, 44 | "../node_modules/typescript/lib/lib.es2015.reflect.d.ts": { 45 | "version": "f5b242136ae9bfb1cc99a5971cccc44e99947ae6b5ef6fd8aa54b5ade553b976", 46 | "signature": "f5b242136ae9bfb1cc99a5971cccc44e99947ae6b5ef6fd8aa54b5ade553b976" 47 | }, 48 | "../node_modules/typescript/lib/lib.es2015.symbol.d.ts": { 49 | "version": "9ae2860252d6b5f16e2026d8a2c2069db7b2a3295e98b6031d01337b96437230", 50 | "signature": "9ae2860252d6b5f16e2026d8a2c2069db7b2a3295e98b6031d01337b96437230" 51 | }, 52 | "../node_modules/typescript/lib/lib.es2015.symbol.wellknown.d.ts": { 53 | "version": "3e0a459888f32b42138d5a39f706ff2d55d500ab1031e0988b5568b0f67c2303", 54 | "signature": "3e0a459888f32b42138d5a39f706ff2d55d500ab1031e0988b5568b0f67c2303" 55 | }, 56 | "../node_modules/typescript/lib/lib.es2016.array.include.d.ts": { 57 | "version": "3f96f1e570aedbd97bf818c246727151e873125d0512e4ae904330286c721bc0", 58 | "signature": "3f96f1e570aedbd97bf818c246727151e873125d0512e4ae904330286c721bc0" 59 | }, 60 | "../node_modules/typescript/lib/lib.es2017.object.d.ts": { 61 | "version": "c2d60b2e558d44384e4704b00e6b3d154334721a911f094d3133c35f0917b408", 62 | "signature": "c2d60b2e558d44384e4704b00e6b3d154334721a911f094d3133c35f0917b408" 63 | }, 64 | "../node_modules/typescript/lib/lib.es2017.sharedmemory.d.ts": { 65 | "version": "b8667586a618c5cf64523d4e500ae39e781428abfb28f3de441fc66b56144b6f", 66 | "signature": "b8667586a618c5cf64523d4e500ae39e781428abfb28f3de441fc66b56144b6f" 67 | }, 68 | "../node_modules/typescript/lib/lib.es2017.string.d.ts": { 69 | "version": "21df2e0059f14dcb4c3a0e125859f6b6ff01332ee24b0065a741d121250bc71c", 70 | "signature": "21df2e0059f14dcb4c3a0e125859f6b6ff01332ee24b0065a741d121250bc71c" 71 | }, 72 | "../node_modules/typescript/lib/lib.es2017.intl.d.ts": { 73 | "version": "c1759cb171c7619af0d2234f2f8fb2a871ee88e956e2ed91bb61778e41f272c6", 74 | "signature": "c1759cb171c7619af0d2234f2f8fb2a871ee88e956e2ed91bb61778e41f272c6" 75 | }, 76 | "../node_modules/typescript/lib/lib.es2017.typedarrays.d.ts": { 77 | "version": "28569d59e07d4378cb3d54979c4c60f9f06305c9bb6999ffe6cab758957adc46", 78 | "signature": "28569d59e07d4378cb3d54979c4c60f9f06305c9bb6999ffe6cab758957adc46" 79 | }, 80 | "../utils.ts": { 81 | "version": "aff8eab24b8762f9207bb9a2e3da4da7a90bc09e3bc6a0f90b0d9655aa0e6f33", 82 | "signature": "b853668bc2224938e1ded2841fccf63d304ac618fac9d87ba38e2760de67dd7b" 83 | }, 84 | "../node_modules/immutable/dist/immutable-nonambient.d.ts": { 85 | "version": "e5e65d1d223b004f5c19145b39f1359d233cbdbaaa6dd576e42e87fd0a5a66e6", 86 | "signature": "e5e65d1d223b004f5c19145b39f1359d233cbdbaaa6dd576e42e87fd0a5a66e6" 87 | }, 88 | "../database.ts": { 89 | "version": "f6debf48fc8831fca70aca8cdcde342c9f3e25024c8f3d515b4178bf2c2d8582", 90 | "signature": "aff823355258bc513c118ac2b5fcf9a5e470396d5040a72303afb192693ca861" 91 | }, 92 | "../sample.ts": { 93 | "version": "5de06176791e3cddaf4dcc2e796281fee8984f358545eb1bd250b1309e2026b4", 94 | "signature": "b9a42acbb76def9980ad97366bfa9a507255fbe6a2c9f827833055546d1461be" 95 | }, 96 | "../app.ts": { 97 | "version": "208a8ad932c8fabafca68d23ae3e73c5ca36062334e740ae238b58ebad03ef77", 98 | "signature": "39f5643dde7ffad6339b41c1b04ba923734a0e99f6730e39a36f0cac6aa507d9" 99 | }, 100 | "../node_modules/@types/node/inspector.d.ts": { 101 | "version": "7e49dbf1543b3ee54853ade4c5e9fa460b6a4eca967efe6bf943e0c505d087ed", 102 | "signature": "7e49dbf1543b3ee54853ade4c5e9fa460b6a4eca967efe6bf943e0c505d087ed" 103 | }, 104 | "../node_modules/@types/node/base.d.ts": { 105 | "version": "9eb6ed7f67245f815a70e718cb2396070890ce589978cb5ae9738fa2287a9c72", 106 | "signature": "9eb6ed7f67245f815a70e718cb2396070890ce589978cb5ae9738fa2287a9c72" 107 | }, 108 | "../node_modules/@types/node/ts3.2/index.d.ts": { 109 | "version": "1de0ff6200b92798a5aef43f57029c79dbf69932037dee1c007fdd2c562db258", 110 | "signature": "1de0ff6200b92798a5aef43f57029c79dbf69932037dee1c007fdd2c562db258" 111 | } 112 | }, 113 | "options": { 114 | "incremental": true, 115 | "module": 1, 116 | "target": 2, 117 | "lib": [ 118 | "lib.es2015.d.ts" 119 | ], 120 | "sourceMap": true, 121 | "strict": true, 122 | "declaration": true, 123 | "outDir": "./", 124 | "project": "../tsconfig.json", 125 | "configFilePath": "../tsconfig.json" 126 | }, 127 | "referencedMap": { 128 | "../node_modules/typescript/lib/lib.es5.d.ts": [ 129 | "../node_modules/@types/node/base.d.ts", 130 | "../node_modules/@types/node/ts3.2/index.d.ts" 131 | ], 132 | "../node_modules/typescript/lib/lib.es2015.d.ts": [ 133 | "../node_modules/@types/node/base.d.ts", 134 | "../node_modules/@types/node/ts3.2/index.d.ts" 135 | ], 136 | "../node_modules/typescript/lib/lib.es2016.d.ts": [ 137 | "../node_modules/@types/node/base.d.ts", 138 | "../node_modules/@types/node/ts3.2/index.d.ts" 139 | ], 140 | "../node_modules/typescript/lib/lib.es2017.d.ts": [ 141 | "../node_modules/@types/node/base.d.ts", 142 | "../node_modules/@types/node/ts3.2/index.d.ts" 143 | ], 144 | "../node_modules/typescript/lib/lib.es2015.core.d.ts": [ 145 | "../node_modules/@types/node/base.d.ts", 146 | "../node_modules/@types/node/ts3.2/index.d.ts" 147 | ], 148 | "../node_modules/typescript/lib/lib.es2015.collection.d.ts": [ 149 | "../node_modules/@types/node/base.d.ts", 150 | "../node_modules/@types/node/ts3.2/index.d.ts" 151 | ], 152 | "../node_modules/typescript/lib/lib.es2015.generator.d.ts": [ 153 | "../node_modules/@types/node/base.d.ts", 154 | "../node_modules/@types/node/ts3.2/index.d.ts" 155 | ], 156 | "../node_modules/typescript/lib/lib.es2015.iterable.d.ts": [ 157 | "../node_modules/@types/node/base.d.ts", 158 | "../node_modules/@types/node/ts3.2/index.d.ts" 159 | ], 160 | "../node_modules/typescript/lib/lib.es2015.promise.d.ts": [ 161 | "../node_modules/@types/node/base.d.ts", 162 | "../node_modules/@types/node/ts3.2/index.d.ts" 163 | ], 164 | "../node_modules/typescript/lib/lib.es2015.proxy.d.ts": [ 165 | "../node_modules/@types/node/base.d.ts", 166 | "../node_modules/@types/node/ts3.2/index.d.ts" 167 | ], 168 | "../node_modules/typescript/lib/lib.es2015.reflect.d.ts": [ 169 | "../node_modules/@types/node/base.d.ts", 170 | "../node_modules/@types/node/ts3.2/index.d.ts" 171 | ], 172 | "../node_modules/typescript/lib/lib.es2015.symbol.d.ts": [ 173 | "../node_modules/@types/node/base.d.ts", 174 | "../node_modules/@types/node/ts3.2/index.d.ts" 175 | ], 176 | "../node_modules/typescript/lib/lib.es2015.symbol.wellknown.d.ts": [ 177 | "../node_modules/@types/node/base.d.ts", 178 | "../node_modules/@types/node/ts3.2/index.d.ts" 179 | ], 180 | "../node_modules/typescript/lib/lib.es2016.array.include.d.ts": [ 181 | "../node_modules/@types/node/base.d.ts", 182 | "../node_modules/@types/node/ts3.2/index.d.ts" 183 | ], 184 | "../node_modules/typescript/lib/lib.es2017.object.d.ts": [ 185 | "../node_modules/@types/node/base.d.ts", 186 | "../node_modules/@types/node/ts3.2/index.d.ts" 187 | ], 188 | "../node_modules/typescript/lib/lib.es2017.sharedmemory.d.ts": [ 189 | "../node_modules/@types/node/base.d.ts", 190 | "../node_modules/@types/node/ts3.2/index.d.ts" 191 | ], 192 | "../node_modules/typescript/lib/lib.es2017.string.d.ts": [ 193 | "../node_modules/@types/node/base.d.ts", 194 | "../node_modules/@types/node/ts3.2/index.d.ts" 195 | ], 196 | "../node_modules/typescript/lib/lib.es2017.intl.d.ts": [ 197 | "../node_modules/@types/node/base.d.ts", 198 | "../node_modules/@types/node/ts3.2/index.d.ts" 199 | ], 200 | "../node_modules/typescript/lib/lib.es2017.typedarrays.d.ts": [ 201 | "../node_modules/@types/node/base.d.ts", 202 | "../node_modules/@types/node/ts3.2/index.d.ts" 203 | ], 204 | "../utils.ts": [ 205 | "../node_modules/@types/node/base.d.ts", 206 | "../node_modules/@types/node/ts3.2/index.d.ts" 207 | ], 208 | "../node_modules/immutable/dist/immutable-nonambient.d.ts": [ 209 | "../node_modules/@types/node/base.d.ts", 210 | "../node_modules/@types/node/ts3.2/index.d.ts" 211 | ], 212 | "../database.ts": [ 213 | "../node_modules/immutable/dist/immutable-nonambient.d.ts", 214 | "../utils.ts", 215 | "../node_modules/@types/node/base.d.ts", 216 | "../node_modules/@types/node/ts3.2/index.d.ts" 217 | ], 218 | "../sample.ts": [ 219 | "../node_modules/immutable/dist/immutable-nonambient.d.ts", 220 | "../database.ts", 221 | "../node_modules/@types/node/base.d.ts", 222 | "../node_modules/@types/node/ts3.2/index.d.ts" 223 | ], 224 | "../app.ts": [ 225 | "../utils.ts", 226 | "../database.ts", 227 | "../sample.ts", 228 | "../node_modules/@types/node/base.d.ts", 229 | "../node_modules/@types/node/ts3.2/index.d.ts" 230 | ], 231 | "../node_modules/@types/node/inspector.d.ts": [ 232 | "../node_modules/@types/node/base.d.ts", 233 | "../node_modules/@types/node/ts3.2/index.d.ts" 234 | ], 235 | "../node_modules/@types/node/base.d.ts": [ 236 | "../node_modules/@types/node/base.d.ts", 237 | "../node_modules/@types/node/inspector.d.ts", 238 | "../node_modules/@types/node/ts3.2/index.d.ts" 239 | ], 240 | "../node_modules/@types/node/ts3.2/index.d.ts": [ 241 | "../node_modules/@types/node/base.d.ts" 242 | ] 243 | }, 244 | "exportedModulesMap": { 245 | "../node_modules/typescript/lib/lib.es5.d.ts": [ 246 | "../node_modules/@types/node/base.d.ts", 247 | "../node_modules/@types/node/ts3.2/index.d.ts" 248 | ], 249 | "../node_modules/typescript/lib/lib.es2015.d.ts": [ 250 | "../node_modules/@types/node/base.d.ts", 251 | "../node_modules/@types/node/ts3.2/index.d.ts" 252 | ], 253 | "../node_modules/typescript/lib/lib.es2016.d.ts": [ 254 | "../node_modules/@types/node/base.d.ts", 255 | "../node_modules/@types/node/ts3.2/index.d.ts" 256 | ], 257 | "../node_modules/typescript/lib/lib.es2017.d.ts": [ 258 | "../node_modules/@types/node/base.d.ts", 259 | "../node_modules/@types/node/ts3.2/index.d.ts" 260 | ], 261 | "../node_modules/typescript/lib/lib.es2015.core.d.ts": [ 262 | "../node_modules/@types/node/base.d.ts", 263 | "../node_modules/@types/node/ts3.2/index.d.ts" 264 | ], 265 | "../node_modules/typescript/lib/lib.es2015.collection.d.ts": [ 266 | "../node_modules/@types/node/base.d.ts", 267 | "../node_modules/@types/node/ts3.2/index.d.ts" 268 | ], 269 | "../node_modules/typescript/lib/lib.es2015.generator.d.ts": [ 270 | "../node_modules/@types/node/base.d.ts", 271 | "../node_modules/@types/node/ts3.2/index.d.ts" 272 | ], 273 | "../node_modules/typescript/lib/lib.es2015.iterable.d.ts": [ 274 | "../node_modules/@types/node/base.d.ts", 275 | "../node_modules/@types/node/ts3.2/index.d.ts" 276 | ], 277 | "../node_modules/typescript/lib/lib.es2015.promise.d.ts": [ 278 | "../node_modules/@types/node/base.d.ts", 279 | "../node_modules/@types/node/ts3.2/index.d.ts" 280 | ], 281 | "../node_modules/typescript/lib/lib.es2015.proxy.d.ts": [ 282 | "../node_modules/@types/node/base.d.ts", 283 | "../node_modules/@types/node/ts3.2/index.d.ts" 284 | ], 285 | "../node_modules/typescript/lib/lib.es2015.reflect.d.ts": [ 286 | "../node_modules/@types/node/base.d.ts", 287 | "../node_modules/@types/node/ts3.2/index.d.ts" 288 | ], 289 | "../node_modules/typescript/lib/lib.es2015.symbol.d.ts": [ 290 | "../node_modules/@types/node/base.d.ts", 291 | "../node_modules/@types/node/ts3.2/index.d.ts" 292 | ], 293 | "../node_modules/typescript/lib/lib.es2015.symbol.wellknown.d.ts": [ 294 | "../node_modules/@types/node/base.d.ts", 295 | "../node_modules/@types/node/ts3.2/index.d.ts" 296 | ], 297 | "../node_modules/typescript/lib/lib.es2016.array.include.d.ts": [ 298 | "../node_modules/@types/node/base.d.ts", 299 | "../node_modules/@types/node/ts3.2/index.d.ts" 300 | ], 301 | "../node_modules/typescript/lib/lib.es2017.object.d.ts": [ 302 | "../node_modules/@types/node/base.d.ts", 303 | "../node_modules/@types/node/ts3.2/index.d.ts" 304 | ], 305 | "../node_modules/typescript/lib/lib.es2017.sharedmemory.d.ts": [ 306 | "../node_modules/@types/node/base.d.ts", 307 | "../node_modules/@types/node/ts3.2/index.d.ts" 308 | ], 309 | "../node_modules/typescript/lib/lib.es2017.string.d.ts": [ 310 | "../node_modules/@types/node/base.d.ts", 311 | "../node_modules/@types/node/ts3.2/index.d.ts" 312 | ], 313 | "../node_modules/typescript/lib/lib.es2017.intl.d.ts": [ 314 | "../node_modules/@types/node/base.d.ts", 315 | "../node_modules/@types/node/ts3.2/index.d.ts" 316 | ], 317 | "../node_modules/typescript/lib/lib.es2017.typedarrays.d.ts": [ 318 | "../node_modules/@types/node/base.d.ts", 319 | "../node_modules/@types/node/ts3.2/index.d.ts" 320 | ], 321 | "../node_modules/immutable/dist/immutable-nonambient.d.ts": [ 322 | "../node_modules/@types/node/base.d.ts", 323 | "../node_modules/@types/node/ts3.2/index.d.ts" 324 | ], 325 | "../database.ts": [ 326 | "../node_modules/immutable/dist/immutable-nonambient.d.ts", 327 | "../utils.ts" 328 | ], 329 | "../app.ts": [ 330 | "../utils.ts", 331 | "../database.ts" 332 | ], 333 | "../node_modules/@types/node/inspector.d.ts": [ 334 | "../node_modules/@types/node/base.d.ts", 335 | "../node_modules/@types/node/ts3.2/index.d.ts" 336 | ], 337 | "../node_modules/@types/node/base.d.ts": [ 338 | "../node_modules/@types/node/base.d.ts", 339 | "../node_modules/@types/node/inspector.d.ts", 340 | "../node_modules/@types/node/ts3.2/index.d.ts" 341 | ], 342 | "../node_modules/@types/node/ts3.2/index.d.ts": [ 343 | "../node_modules/@types/node/base.d.ts" 344 | ] 345 | }, 346 | "semanticDiagnosticsPerFile": [ 347 | "../node_modules/typescript/lib/lib.es5.d.ts", 348 | "../node_modules/typescript/lib/lib.es2015.d.ts", 349 | "../node_modules/typescript/lib/lib.es2016.d.ts", 350 | "../node_modules/typescript/lib/lib.es2017.d.ts", 351 | "../node_modules/typescript/lib/lib.es2015.core.d.ts", 352 | "../node_modules/typescript/lib/lib.es2015.collection.d.ts", 353 | "../node_modules/typescript/lib/lib.es2015.generator.d.ts", 354 | "../node_modules/typescript/lib/lib.es2015.iterable.d.ts", 355 | "../node_modules/typescript/lib/lib.es2015.promise.d.ts", 356 | "../node_modules/typescript/lib/lib.es2015.proxy.d.ts", 357 | "../node_modules/typescript/lib/lib.es2015.reflect.d.ts", 358 | "../node_modules/typescript/lib/lib.es2015.symbol.d.ts", 359 | "../node_modules/typescript/lib/lib.es2015.symbol.wellknown.d.ts", 360 | "../node_modules/typescript/lib/lib.es2016.array.include.d.ts", 361 | "../node_modules/typescript/lib/lib.es2017.object.d.ts", 362 | "../node_modules/typescript/lib/lib.es2017.sharedmemory.d.ts", 363 | "../node_modules/typescript/lib/lib.es2017.string.d.ts", 364 | "../node_modules/typescript/lib/lib.es2017.intl.d.ts", 365 | "../node_modules/typescript/lib/lib.es2017.typedarrays.d.ts", 366 | "../utils.ts", 367 | "../node_modules/immutable/dist/immutable-nonambient.d.ts", 368 | "../database.ts", 369 | "../sample.ts", 370 | "../node_modules/@types/node/inspector.d.ts", 371 | "../node_modules/@types/node/base.d.ts", 372 | "../node_modules/@types/node/ts3.2/index.d.ts", 373 | "../app.ts" 374 | ] 375 | }, 376 | "version": "3.8.3" 377 | } -------------------------------------------------------------------------------- /out/utils.d.ts: -------------------------------------------------------------------------------- 1 | export declare type Without = Pick>; 2 | export declare type Fun = (_: a) => b; 3 | export declare const Without: (key: K, { [key]: _, ...values }: T) => Pick>; 4 | export declare const Rename: (keyOld: KOld, keyNew: KNew, { [keyOld]: value, ...values }: T) => Pick> & { [x in KNew]: T[KOld]; }; 5 | -------------------------------------------------------------------------------- /out/utils.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __rest = (this && this.__rest) || function (s, e) { 3 | var t = {}; 4 | for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) 5 | t[p] = s[p]; 6 | if (s != null && typeof Object.getOwnPropertySymbols === "function") 7 | for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) { 8 | if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) 9 | t[p[i]] = s[p[i]]; 10 | } 11 | return t; 12 | }; 13 | Object.defineProperty(exports, "__esModule", { value: true }); 14 | exports.Without = (key, _a) => { 15 | var _b = key, _ = _a[_b], values = __rest(_a, [typeof _b === "symbol" ? _b : _b + ""]); 16 | return values; 17 | }; 18 | exports.Rename = (keyOld, keyNew, _a) => { 19 | var _b = keyOld, value = _a[_b], values = __rest(_a, [typeof _b === "symbol" ? _b : _b + ""]); 20 | return (Object.assign(Object.assign({}, values), { [keyNew]: value })); 21 | }; 22 | //# sourceMappingURL=utils.js.map -------------------------------------------------------------------------------- /out/utils.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"utils.js","sourceRoot":"","sources":["../utils.ts"],"names":[],"mappings":";;;;;;;;;;;;;AAGa,QAAA,OAAO,GAAG,CAAuB,GAAM,EAAE,EAA0B,EAAiB,EAAE;QAA3C,QAAK,EAAL,UAAQ,EAAE,4DAAS;IAAyB,OAAA,MAAM,CAAA;CAAA,CAAA;AAC7F,QAAA,MAAM,GAAG,CAA+C,MAAY,EAAE,MAAY,EAAE,EAAiC,EACpF,EAAE;QADmD,WAAQ,EAAR,cAAe,EAAE,4DAAS;IAE3H,OAAA,iCAAM,MAAM,GAAK,EAAE,CAAC,MAAM,CAAC,EAAE,KAAK,EAA8B,EAAG,CAAA;CAAA,CAAA"} -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hierarchy-processor", 3 | "version": "0.0.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "@types/node": { 8 | "version": "8.10.59", 9 | "resolved": "https://registry.npmjs.org/@types/node/-/node-8.10.59.tgz", 10 | "integrity": "sha512-8RkBivJrDCyPpBXhVZcjh7cQxVBSmRk9QM7hOketZzp6Tg79c0N8kkpAIito9bnJ3HCVCHVYz+KHTEbfQNfeVQ==", 11 | "dev": true 12 | }, 13 | "immutable": { 14 | "version": "4.0.0-rc.12", 15 | "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.0.0-rc.12.tgz", 16 | "integrity": "sha512-0M2XxkZLx/mi3t8NVwIm1g8nHoEmM9p9UBl/G9k4+hm0kBgOVdMV/B3CY5dQ8qG8qc80NN4gDV4HQv6FTJ5q7A==" 17 | }, 18 | "typescript": { 19 | "version": "3.7.3", 20 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.7.3.tgz", 21 | "integrity": "sha512-Mcr/Qk7hXqFBXMN7p7Lusj1ktCBydylfQM/FZCk5glCNQJrCUKPkMHdo9R0MTFWsC/4kPFvDS0fDPvukfCkFsw==", 22 | "dev": true 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ts-in-memory-database", 3 | "version": "0.9.2", 4 | "description": "Typesafe in-memory database", 5 | "main": "./out/app.js", 6 | "types": "./out/app.d.ts", 7 | "author": { 8 | "name": "Dr. Giuseppe Maggiore" 9 | }, 10 | "scripts": { 11 | "build": "tsc --build", 12 | "watch": "tsc --build", 13 | "clean": "tsc --build --clean" 14 | }, 15 | "devDependencies": { 16 | "@types/node": "^8.0.14", 17 | "typescript": "^3.2.2" 18 | }, 19 | "dependencies": { 20 | "immutable": "^4.0.0-rc.12" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /sample1.ts: -------------------------------------------------------------------------------- 1 | import { Record, List, Map, Set, is } from "immutable" 2 | import { Entity, Relation, Inverted, EntityRelations, Database } from "./database" 3 | 4 | interface Person { 5 | Name: string 6 | Surname: string 7 | Age: number 8 | } 9 | 10 | interface MyEntities { 11 | People: Entity<{ PersonId: number }, Person, { 12 | }, MyEntities> 13 | } 14 | 15 | const PersonId = Record({ PersonId: 0 }) 16 | 17 | const myEntities: MyEntities = { 18 | People: Entity()(Map, Person>() 19 | .set(PersonId({ PersonId: 0 }), { Name: "John", Surname: "Doe", Age: 27 }) 20 | .set(PersonId({ PersonId: 1 }), { Name: "Jane", Surname: "Doe", Age: 31 }) 21 | .set(PersonId({ PersonId: 2 }), { Name: "Giuseppe", Surname: "Rossi", Age: 35}) 22 | .set(PersonId({ PersonId: 3 }), { Name: "Giulia", Surname: "Verdi", Age: 34 }) 23 | .set(PersonId({ PersonId: 4 }), { Name: "Reby", Surname: "Rossi", Age: 7 }) 24 | .set(PersonId({ PersonId: 5 }), { Name: "Rechy", Surname: "Rossi", Age: 5 }) 25 | .set(PersonId({ PersonId: 6 }), { Name: "Francis", Surname: "Dee Jay", Age: 35 }) 26 | , 27 | { 28 | }) 29 | } 30 | 31 | export const test = () => { 32 | const db: Database = Database(myEntities) 33 | const q0 = db.from("People").select("Name", "Surname") 34 | const q1 = db.from("People").filter(p => p.Age >= 18) 35 | const q2 = db.from("People").filter(p => p.Age >= 18).select("Name") 36 | const q3 = db.from("People").fieldAs("Name", "FirstName") 37 | const q4 = db.from("People").fieldAs("Name", "FirstName").select("FirstName").filter(p => p.FirstName.startsWith("Giu")) 38 | 39 | const v0 = q0.values().toArray().map(x => x[1]) 40 | const v1 = q1.values().toArray().map(x => x[1]) 41 | const v2 = q2.values().toArray().map(x => x[1]) 42 | const v3 = q3.values().toArray().map(x => x[1]) 43 | const v4 = q4.values().toArray().map(x => x[1]) 44 | 45 | const results = [v0, v1, v2, v3, v4].map(v => JSON.stringify(v, null, 1)) 46 | 47 | results.forEach(r => console.log(r)) 48 | console.log(`Done.`) 49 | } 50 | -------------------------------------------------------------------------------- /sample2.ts: -------------------------------------------------------------------------------- 1 | import { Record, List, Map, Set, is } from "immutable" 2 | import { Entity, Relation, Inverted, EntityRelations, Database } from "./database" 3 | 4 | interface Person { 5 | Name: string 6 | Surname: string 7 | Age: number 8 | } 9 | 10 | interface Address { 11 | Street: string 12 | Number: number 13 | Postcode: string 14 | } 15 | 16 | interface City { 17 | Name: string 18 | Population: number 19 | } 20 | 21 | interface MyEntities { 22 | People: Entity<{ PersonId: number }, Person, { 23 | Addresses: Relation 24 | }, MyEntities> 25 | Addresses: Entity<{ AddressId: number }, Address, { 26 | Cities: Relation, 27 | People: Inverted["Addresses"]> 28 | }, MyEntities> 29 | Cities: Entity<{ CityId: number }, City, { 30 | Addresses: Inverted["Cities"]> 31 | }, MyEntities> 32 | } 33 | 34 | const PersonId = Record({ PersonId: 0 }) 35 | const AddressId = Record({ AddressId: 0 }) 36 | const CityId = Record({ CityId: 0 }) 37 | 38 | const personAddresses = Relation( 39 | Map, Set>>() 40 | .set(PersonId({ PersonId: 0 }), Set>() 41 | .add(AddressId({ AddressId: 0 }))) 42 | .set(PersonId({ PersonId: 1 }), Set>() 43 | .add(AddressId({ AddressId: 0 }))) 44 | .set(PersonId({ PersonId: 2 }), Set>() 45 | .add(AddressId({ AddressId: 1 }))) 46 | .set(PersonId({ PersonId: 3 }), Set>() 47 | .add(AddressId({ AddressId: 1 }))) 48 | .set(PersonId({ PersonId: 4 }), Set>() 49 | .add(AddressId({ AddressId: 1 }))) 50 | .set(PersonId({ PersonId: 5 }), Set>() 51 | .add(AddressId({ AddressId: 1 }))) 52 | .set(PersonId({ PersonId: 6 }), Set>() 53 | .add(AddressId({ AddressId: 2 }))) 54 | ) 55 | 56 | const addressCities = Relation( 57 | Map, Set>>() 58 | .set(AddressId({ AddressId: 0 }), Set>() 59 | .add(CityId({ CityId: 0 }))) 60 | .set(AddressId({ AddressId: 1 }), Set>() 61 | .add(CityId({ CityId: 1 }))) 62 | .set(AddressId({ AddressId: 2 }), Set>() 63 | .add(CityId({ CityId: 2 }))) 64 | ) 65 | 66 | const myEntities: MyEntities = { 67 | People: Entity()(Map, Person>() 68 | .set(PersonId({ PersonId: 0 }), { Name: "John", Surname: "Doe", Age: 27 }) 69 | .set(PersonId({ PersonId: 1 }), { Name: "Jane", Surname: "Doe", Age: 31 }) 70 | .set(PersonId({ PersonId: 2 }), { Name: "Giuseppe", Surname: "Rossi", Age: 35}) 71 | .set(PersonId({ PersonId: 3 }), { Name: "Giulia", Surname: "Verdi", Age: 34 }) 72 | .set(PersonId({ PersonId: 4 }), { Name: "Reby", Surname: "Rossi", Age: 7 }) 73 | .set(PersonId({ PersonId: 5 }), { Name: "Rechy", Surname: "Rossi", Age: 5 }) 74 | .set(PersonId({ PersonId: 6 }), { Name: "Francis", Surname: "Dee Jay", Age: 35 }) 75 | , 76 | { 77 | Addresses: personAddresses 78 | }), 79 | Addresses: Entity()(Map, Address>() 80 | .set(AddressId({ AddressId: 0 }), { Street: "Afrikaanderplein", Number: 7, Postcode: "3072 EA" }) 81 | .set(AddressId({ AddressId: 1 }), { Street: "Kalverstraat", Number: 92, Postcode: "1012 PH" }) 82 | .set(AddressId({ AddressId: 2 }), { Street: "Heidelaan", Number: 139, Postcode: "3851 EX" }) 83 | , 84 | { 85 | People: personAddresses.Inverted(), 86 | Cities: addressCities, 87 | }), 88 | // sort of random values, did not check if they are accurate :) 89 | Cities: Entity()(Map, City>() 90 | .set(CityId({ CityId: 0 }), { Name: "Rotterdam", Population: 1250000 }) 91 | .set(CityId({ CityId: 1 }), { Name: "Amsterdam", Population: 1250000 }) 92 | .set(CityId({ CityId: 2 }), { Name: "Hengelo", Population: 150000 }) 93 | , 94 | { 95 | Addresses: addressCities.Inverted() 96 | }) 97 | } 98 | 99 | export const test = () => { 100 | const db: Database = Database(myEntities) 101 | const q0 = db.from("People").fieldAs("Name", "FirstName").select("FirstName").filter(p => p.FirstName.startsWith("Giu")) 102 | const q1 = db.from("People").expand(db, "Addresses", a => a.select("Street", "Number")) 103 | const q2 = db.from("People").join(db, "Addresses", a => a.select("Street", "Number").join(db, "Cities", c => c.fieldAs("Name", "CityName"))) 104 | const q3 = db.from("Cities").expand(db, "Addresses", a => a.expand(db, "People", p => p)) 105 | const q4 = db.from("Cities").expand(db, "Addresses", a => a.expandAs(db, "People", "Inhabitants", p => p)) 106 | const q5 = db.from("People").join(db, "Addresses", a => a.select().expand(db, "Cities", c => c)) 107 | 108 | const v0 = q0.values().toArray().map(x => x[1]) 109 | const v1 = q1.values().toArray().map(x => ({ ...x[1], Addresses: x[1].Addresses.toArray().map(a => a[1]) })) 110 | const v2 = q2.values().toArray().map(x => x[1]) 111 | const v3 = q3.values().toArray().map(x => ({ ...x[1], Addresses: x[1].Addresses.toArray().map(a => ({ ...a[1], People: a[1].People.toArray().map(p => p[1]) })) })) 112 | const v4 = q4.values().toArray().map(x => ({ ...x[1], Addresses: x[1].Addresses.toArray().map(a => ({ ...a[1], Inhabitants: a[1].Inhabitants.toArray().map(p => p[1]) })) })) 113 | const v5 = q5.values().toArray().map(x => ({ ...x[1], Cities: x[1].Cities.toArray().map(c => c[1]) })) 114 | 115 | const results = [v0, v1, v2, v3, v4, v5].map(v => JSON.stringify(v, null, 1)) 116 | 117 | results.forEach(r => console.log(r)) 118 | console.log(`Done.`) 119 | } -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "incremental": true, 4 | "module": "commonjs", 5 | "target": "es6", 6 | "lib": [ "es6" ], 7 | "sourceMap": true, 8 | "strict": true, 9 | "declaration": true, 10 | "outDir": "./out" 11 | }, 12 | "exclude": [ 13 | "node_modules", 14 | "out" 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /utils.ts: -------------------------------------------------------------------------------- 1 | export type Without = Pick> 2 | export type Fun = (_: a) => b 3 | 4 | export const Without = (key: K, { [key]: _, ...values }: T): Without => values 5 | export const Rename = (keyOld: KOld, keyNew: KNew, { [keyOld]: value, ...values }: T): 6 | Without & { [x in KNew]: T[KOld] } => 7 | ({ ...values, ...{ [keyNew]: value } as { [x in KNew]: T[KOld] } }) 8 | --------------------------------------------------------------------------------