@powerhousedao/academy 0.1.0-dev.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.vscode/settings.json +3 -0
- package/CHANGELOG.md +9 -0
- package/Dockerfile +31 -0
- package/ProcFile +1 -0
- package/README.md +43 -0
- package/babel.config.js +3 -0
- package/blog/BeyondCommunication-ABlueprintForDevelopment.md +88 -0
- package/blog/TheChallengeOfChange.md +86 -0
- package/blog/images/Iteration.png +0 -0
- package/blog/images/RAD.png +0 -0
- package/docs/academy/01-GetStarted/00-GetStarted.mdx +181 -0
- package/docs/academy/01-GetStarted/01_InstallDemoPackage.md +38 -0
- package/docs/academy/01-GetStarted/02-ToDoList/01-CreateNewPowerhouseProject.md +97 -0
- package/docs/academy/01-GetStarted/02-ToDoList/02-DefineToDoListDocumentModel.md +86 -0
- package/docs/academy/01-GetStarted/02-ToDoList/03-ImplementOperationReducers.md +201 -0
- package/docs/academy/01-GetStarted/02-ToDoList/04-BuildToDoListEditor.md +494 -0
- package/docs/academy/01-GetStarted/02-ToDoList/_category_.json +8 -0
- package/docs/academy/01-GetStarted/02-ToDoList/images/DocumentModelHeader.png +0 -0
- package/docs/academy/01-GetStarted/02-ToDoList/images/DocumentModelOperations.png +0 -0
- package/docs/academy/01-GetStarted/02-ToDoList/images/OpenDocumentModelEditor.gif +0 -0
- package/docs/academy/01-GetStarted/02-ToDoList/images/completeEditor.png +0 -0
- package/docs/academy/01-GetStarted/02-ToDoList/images/connectApp.gif +0 -0
- package/docs/academy/01-GetStarted/02-ToDoList/images/form.png +0 -0
- package/docs/academy/01-GetStarted/02-ToDoList/images/mytodolist.gif +0 -0
- package/docs/academy/01-GetStarted/02-ToDoList/images/reducers.png +0 -0
- package/docs/academy/01-GetStarted/02-ToDoList/images/vscode.png +0 -0
- package/docs/academy/01-GetStarted/styles.module.css +99 -0
- package/docs/academy/02-AdvancedTutorial/01-Create/00-BuilderTools.md +234 -0
- package/docs/academy/02-AdvancedTutorial/01-Create/01-SetupBuilderEnvironment.md +247 -0
- package/docs/academy/02-AdvancedTutorial/01-Create/02-MoreTutorials/04-UtilitiesAndTips.md +79 -0
- package/docs/academy/02-AdvancedTutorial/01-Create/02-MoreTutorials/Chatroom/01-SetupBuilderEnvironment.md +216 -0
- package/docs/academy/02-AdvancedTutorial/01-Create/02-MoreTutorials/Chatroom/02-CreateNewPowerhouseProject.md +78 -0
- package/docs/academy/02-AdvancedTutorial/01-Create/02-MoreTutorials/Chatroom/03-DefineChatroomDocumentModel.md +139 -0
- package/docs/academy/02-AdvancedTutorial/01-Create/02-MoreTutorials/Chatroom/04-ImplementOperationReducers.md +364 -0
- package/docs/academy/02-AdvancedTutorial/01-Create/02-MoreTutorials/Chatroom/05-ImplementChatroomEditor.md +194 -0
- package/docs/academy/02-AdvancedTutorial/01-Create/02-MoreTutorials/Chatroom/06-LaunchALocalReactor.md +15 -0
- package/docs/academy/02-AdvancedTutorial/01-Create/02-MoreTutorials/Chatroom/_category_.json +8 -0
- package/docs/academy/02-AdvancedTutorial/01-Create/02-MoreTutorials/Chatroom/image-1.png +0 -0
- package/docs/academy/02-AdvancedTutorial/01-Create/02-MoreTutorials/Chatroom/image-2.png +0 -0
- package/docs/academy/02-AdvancedTutorial/01-Create/02-MoreTutorials/Chatroom/image-3.png +0 -0
- package/docs/academy/02-AdvancedTutorial/01-Create/02-MoreTutorials/Chatroom/image-4.png +0 -0
- package/docs/academy/02-AdvancedTutorial/01-Create/02-MoreTutorials/Chatroom/image-5.png +0 -0
- package/docs/academy/02-AdvancedTutorial/01-Create/02-MoreTutorials/Chatroom/image.png +0 -0
- package/docs/academy/02-AdvancedTutorial/01-Create/02-MoreTutorials/Chatroom/images/ChatRoomConnectApp.gif +0 -0
- package/docs/academy/02-AdvancedTutorial/01-Create/02-MoreTutorials/Chatroom/images/ChatRoomTest.gif +0 -0
- package/docs/academy/02-AdvancedTutorial/01-Create/02-MoreTutorials/Chatroom/images/completeEditor.png +0 -0
- package/docs/academy/02-AdvancedTutorial/01-Create/02-MoreTutorials/Chatroom/images/form.png +0 -0
- package/docs/academy/02-AdvancedTutorial/01-Create/02-MoreTutorials/Chatroom/images/reducers.png +0 -0
- package/docs/academy/02-AdvancedTutorial/01-Create/02-MoreTutorials/Chatroom/images/vscode.png +0 -0
- package/docs/academy/02-AdvancedTutorial/01-Create/02-MoreTutorials/_category_.json +8 -0
- package/docs/academy/02-AdvancedTutorial/01-Create/02-StandardDocumentModelWorkflow.md +229 -0
- package/docs/academy/02-AdvancedTutorial/03-BuildingUserExperiences/01-BuildingBeautifulDocumentEditors.md +109 -0
- package/docs/academy/02-AdvancedTutorial/03-BuildingUserExperiences/02-ConfiguringDrives.md +51 -0
- package/docs/academy/02-AdvancedTutorial/03-BuildingUserExperiences/03-BuildingADriveExplorer.md +174 -0
- package/docs/academy/02-AdvancedTutorial/03-BuildingUserExperiences/07-DocumentTools/01-OperationHistory.md +67 -0
- package/docs/academy/02-AdvancedTutorial/03-BuildingUserExperiences/07-DocumentTools/02-RevisionHistoryTimeline.md +132 -0
- package/docs/academy/02-AdvancedTutorial/03-BuildingUserExperiences/07-DocumentTools/_category_.json +7 -0
- package/docs/academy/02-AdvancedTutorial/03-BuildingUserExperiences/07-DocumentTools/images/committer-address-popup.png +0 -0
- package/docs/academy/02-AdvancedTutorial/03-BuildingUserExperiences/07-DocumentTools/images/revision-hash-popup.png +0 -0
- package/docs/academy/02-AdvancedTutorial/03-BuildingUserExperiences/07-DocumentTools/images/revision-history-list.png +0 -0
- package/docs/academy/02-AdvancedTutorial/03-BuildingUserExperiences/07-DocumentTools/images/signature-details-popup.png +0 -0
- package/docs/academy/02-AdvancedTutorial/03-BuildingUserExperiences/_category_.json +8 -0
- package/docs/academy/02-AdvancedTutorial/03-BuildingUserExperiences/images/CreateDrive.png +0 -0
- package/docs/academy/02-AdvancedTutorial/03-BuildingUserExperiences/images/mytodolist.gif +0 -0
- package/docs/academy/02-AdvancedTutorial/04-WorkWithData/01-ReadingAndWritingThroughTheAPI.mdx +121 -0
- package/docs/academy/02-AdvancedTutorial/04-WorkWithData/02-GraphQLAtPowerhouse.md +156 -0
- package/docs/academy/02-AdvancedTutorial/04-WorkWithData/03-WorkingWithSubgraphs/02-GraphQLAndSubgraphs.mdx +119 -0
- package/docs/academy/02-AdvancedTutorial/04-WorkWithData/03-WorkingWithSubgraphs/03-WorkingWithSubgraphs.md +312 -0
- package/docs/academy/02-AdvancedTutorial/04-WorkWithData/03-WorkingWithSubgraphs/_category_.json +8 -0
- package/docs/academy/02-AdvancedTutorial/04-WorkWithData/04-analytics-processor.md +342 -0
- package/docs/academy/02-AdvancedTutorial/04-WorkWithData/05-AnalyticsProcessorTutorial/01-SetupBuilderEnvironment.md +215 -0
- package/docs/academy/02-AdvancedTutorial/04-WorkWithData/05-AnalyticsProcessorTutorial/02-CreateNewPowerhouseProject.md +55 -0
- package/docs/academy/02-AdvancedTutorial/04-WorkWithData/05-AnalyticsProcessorTutorial/03-GenerateAnAnalyticsProcessor.md +173 -0
- package/docs/academy/02-AdvancedTutorial/04-WorkWithData/05-AnalyticsProcessorTutorial/04-UpdateAnalyticsProcessor.md +223 -0
- package/docs/academy/02-AdvancedTutorial/04-WorkWithData/05-AnalyticsProcessorTutorial/_category_.json +8 -0
- package/docs/academy/02-AdvancedTutorial/04-WorkWithData/05-AnalyticsProcessorTutorial/images/Create-SPV.gif +0 -0
- package/docs/academy/02-AdvancedTutorial/04-WorkWithData/05-AnalyticsProcessorTutorial/images/Create-a-new-asset.png +0 -0
- package/docs/academy/02-AdvancedTutorial/04-WorkWithData/05-AnalyticsProcessorTutorial/images/Create-a-transaction.gif +0 -0
- package/docs/academy/02-AdvancedTutorial/04-WorkWithData/05-AnalyticsProcessorTutorial/images/Transaction-table.png +0 -0
- package/docs/academy/02-AdvancedTutorial/04-WorkWithData/05-AnalyticsProcessorTutorial/images/create-a-new-RWA-document.gif +0 -0
- package/docs/academy/02-AdvancedTutorial/04-WorkWithData/05-AnalyticsProcessorTutorial/images/granularity.png +0 -0
- package/docs/academy/02-AdvancedTutorial/04-WorkWithData/06-Analytics Engine/GraphQL References/QueryingADocumentWithGraphQL.md +244 -0
- package/docs/academy/02-AdvancedTutorial/04-WorkWithData/06-Analytics Engine/GraphQL References/rwa-reports/listener-raw.png +0 -0
- package/docs/academy/02-AdvancedTutorial/04-WorkWithData/06-Analytics Engine/GraphQL References/rwa-reports/raw-reports1.png +0 -0
- package/docs/academy/02-AdvancedTutorial/04-WorkWithData/06-Analytics Engine/GraphQL References/rwa-reports/raw-reports2.png +0 -0
- package/docs/academy/02-AdvancedTutorial/04-WorkWithData/06-Analytics Engine/GraphQL References/rwa-reports/rwaRegister.png +0 -0
- package/docs/academy/02-AdvancedTutorial/04-WorkWithData/06-Analytics Engine/apse.png +0 -0
- package/docs/academy/02-AdvancedTutorial/04-WorkWithData/06-Analytics Engine/best-practices.md +60 -0
- package/docs/academy/02-AdvancedTutorial/04-WorkWithData/06-Analytics Engine/filter.png +0 -0
- package/docs/academy/02-AdvancedTutorial/04-WorkWithData/06-Analytics Engine/filteroptions.png +0 -0
- package/docs/academy/02-AdvancedTutorial/04-WorkWithData/06-Analytics Engine/graphql/index.md +166 -0
- package/docs/academy/02-AdvancedTutorial/04-WorkWithData/06-Analytics Engine/graphql/integration.md +75 -0
- package/docs/academy/02-AdvancedTutorial/04-WorkWithData/06-Analytics Engine/images/dbs.png +0 -0
- package/docs/academy/02-AdvancedTutorial/04-WorkWithData/06-Analytics Engine/images/high-level.jpg +0 -0
- package/docs/academy/02-AdvancedTutorial/04-WorkWithData/06-Analytics Engine/images/indexeddb.png +0 -0
- package/docs/academy/02-AdvancedTutorial/04-WorkWithData/06-Analytics Engine/images/libs-core.jpg +0 -0
- package/docs/academy/02-AdvancedTutorial/04-WorkWithData/06-Analytics Engine/images/libs.jpg +0 -0
- package/docs/academy/02-AdvancedTutorial/04-WorkWithData/06-Analytics Engine/images/lod.jpg +0 -0
- package/docs/academy/02-AdvancedTutorial/04-WorkWithData/06-Analytics Engine/images/logo.png +0 -0
- package/docs/academy/02-AdvancedTutorial/04-WorkWithData/06-Analytics Engine/images/navbar.png +0 -0
- package/docs/academy/02-AdvancedTutorial/04-WorkWithData/06-Analytics Engine/images/overview-1.jpg +0 -0
- package/docs/academy/02-AdvancedTutorial/04-WorkWithData/06-Analytics Engine/images/overview-2.jpg +0 -0
- package/docs/academy/02-AdvancedTutorial/04-WorkWithData/06-Analytics Engine/images/overview-3.jpg +0 -0
- package/docs/academy/02-AdvancedTutorial/04-WorkWithData/06-Analytics Engine/images/overview-4.jpg +0 -0
- package/docs/academy/02-AdvancedTutorial/04-WorkWithData/06-Analytics Engine/images/overview-5.jpg +0 -0
- package/docs/academy/02-AdvancedTutorial/04-WorkWithData/06-Analytics Engine/images/overview-6.jpg +0 -0
- package/docs/academy/02-AdvancedTutorial/04-WorkWithData/06-Analytics Engine/images/paths-1.jpg +0 -0
- package/docs/academy/02-AdvancedTutorial/04-WorkWithData/06-Analytics Engine/images/paths-2.jpg +0 -0
- package/docs/academy/02-AdvancedTutorial/04-WorkWithData/06-Analytics Engine/intro.md +149 -0
- package/docs/academy/02-AdvancedTutorial/04-WorkWithData/06-Analytics Engine/typescript/benchmarks.md +27 -0
- package/docs/academy/02-AdvancedTutorial/04-WorkWithData/06-Analytics Engine/typescript/browser.md +77 -0
- package/docs/academy/02-AdvancedTutorial/04-WorkWithData/06-Analytics Engine/typescript/compatibility.md +14 -0
- package/docs/academy/02-AdvancedTutorial/04-WorkWithData/06-Analytics Engine/typescript/index.md +230 -0
- package/docs/academy/02-AdvancedTutorial/04-WorkWithData/06-Analytics Engine/typescript/memory.md +72 -0
- package/docs/academy/02-AdvancedTutorial/04-WorkWithData/06-Analytics Engine/typescript/pg.md +63 -0
- package/docs/academy/02-AdvancedTutorial/04-WorkWithData/06-Analytics Engine/typescript/schema.md +14 -0
- package/docs/academy/02-AdvancedTutorial/04-WorkWithData/06-Analytics Engine/typescript/utilities.md +102 -0
- package/docs/academy/02-AdvancedTutorial/04-WorkWithData/06-Analytics Engine/use-cases/index.md +7 -0
- package/docs/academy/02-AdvancedTutorial/04-WorkWithData/06-Analytics Engine/use-cases/maker.md +652 -0
- package/docs/academy/02-AdvancedTutorial/04-WorkWithData/06-Analytics Engine/use-cases/processors.md +3 -0
- package/docs/academy/02-AdvancedTutorial/04-WorkWithData/_category_.json +8 -0
- package/docs/academy/02-AdvancedTutorial/04-WorkWithData/images/OperationHistory.png +0 -0
- package/docs/academy/02-AdvancedTutorial/04-WorkWithData/images/OperationsQuery.png +0 -0
- package/docs/academy/02-AdvancedTutorial/04-WorkWithData/images/QueryDocumentID.png +0 -0
- package/docs/academy/02-AdvancedTutorial/04-WorkWithData/images/SwitchboardButton.png +0 -0
- package/docs/academy/02-AdvancedTutorial/05-Launch/00-IntegrateInAFront-End +3 -0
- package/docs/academy/02-AdvancedTutorial/05-Launch/01-IntroducingFusion +18 -0
- package/docs/academy/02-AdvancedTutorial/05-Launch/02-IntroductionToPackages.md +79 -0
- package/docs/academy/02-AdvancedTutorial/05-Launch/02-PublishYourProject.md +230 -0
- package/docs/academy/02-AdvancedTutorial/05-Launch/03-RunOnACloudServer.md +279 -0
- package/docs/academy/02-AdvancedTutorial/05-Launch/03-SetupEnvironment.md +436 -0
- package/docs/academy/02-AdvancedTutorial/05-Launch/04-GraphQLNamespacing +44 -0
- package/docs/academy/02-AdvancedTutorial/05-Launch/05-LaunchYourBackend.md +3 -0
- package/docs/academy/02-AdvancedTutorial/05-Launch/06-LaunchYourFrontend.md +3 -0
- package/docs/academy/02-AdvancedTutorial/05-Launch/_category_.json +8 -0
- package/docs/academy/02-AdvancedTutorial/05-Launch/images/SSHConnection.png +0 -0
- package/docs/academy/02-AdvancedTutorial/05-Launch/images/homedesign.png +0 -0
- package/docs/academy/02-AdvancedTutorial/05-Launch/images/keyconcepts.png +0 -0
- package/docs/academy/02-AdvancedTutorial/05-Launch/images/tutorialschema.png +0 -0
- package/docs/academy/02-AdvancedTutorial/06-Authorization/Authorization.md +100 -0
- package/docs/academy/02-AdvancedTutorial/_category_.json +7 -0
- package/docs/academy/03-APIReferences/00-PowerhouseCLI.md +1 -0
- package/docs/academy/03-APIReferences/_category_.json +7 -0
- package/docs/academy/04-ComponentLibrary/01-PowerhouseDesignSystem.md +94 -0
- package/docs/academy/04-ComponentLibrary/02-BuildingWithScalars.md +54 -0
- package/docs/academy/04-ComponentLibrary/03-Scalar-Components/01-phid-field.mdx +72 -0
- package/docs/academy/04-ComponentLibrary/03-Scalar-Components/02-input-field.mdx +0 -0
- package/docs/academy/04-ComponentLibrary/04-Complex-Components/01-sidebar.mdx +36 -0
- package/docs/academy/04-ComponentLibrary/05-Layout-Components/01-test-toupdate.mdx +61 -0
- package/docs/academy/04-ComponentLibrary/06-Fragments/01-test-toupdate.mdx +61 -0
- package/docs/academy/04-ComponentLibrary/_category_.json +8 -0
- package/docs/academy/05-Architecture/00-PowerhouseArchitecture.md +50 -0
- package/docs/academy/05-Architecture/01-WorkingWithTheReactor.md +48 -0
- package/docs/academy/05-Architecture/02-ReferencingMonorepoPackages +65 -0
- package/docs/academy/05-Architecture/04-MovingBeyondCRUD +61 -0
- package/docs/academy/05-Architecture/05-DocumentModelTheory/01-WhatIsADocumentModel.md +188 -0
- package/docs/academy/05-Architecture/05-DocumentModelTheory/02-DAOandDocumentsModelsQ+A.md +177 -0
- package/docs/academy/05-Architecture/05-DocumentModelTheory/02-domain-modeling.md +103 -0
- package/docs/academy/05-Architecture/05-DocumentModelTheory/03-BenefitsOfDocumentModels.md +95 -0
- package/docs/academy/05-Architecture/05-DocumentModelTheory/05-best-practices.md +257 -0
- package/docs/academy/05-Architecture/05-DocumentModelTheory/_category_.json +8 -0
- package/docs/academy/05-Architecture/05-DocumentModelTheory/three-data-layers.png +0 -0
- package/docs/academy/05-Architecture/_category_.json +7 -0
- package/docs/academy/05-Architecture/images/image.png +0 -0
- package/docs/academy/06-Cookbook.md +905 -0
- package/docs/academy/07-Glossary.md +50 -0
- package/docs/bookofpowerhouse/01-Overview.md +29 -0
- package/docs/bookofpowerhouse/02-GeneralFrameworkAndPhilosophy.md +15 -0
- package/docs/bookofpowerhouse/03-PowerhouseSoftwareArchitecture.md +33 -0
- package/docs/bookofpowerhouse/04-DevelopmentApproaches.md +36 -0
- package/docs/bookofpowerhouse/05-SNOsandANewModelForOSSandPublicGoods.md +73 -0
- package/docs/bookofpowerhouse/06-SNOsInActionAndPlatformEconomies.md +17 -0
- package/docs/renown/01-intro.md +18 -0
- package/docs/renown/02-renown-login-flow.md +60 -0
- package/docusaurus +0 -0
- package/docusaurus.config.ts +170 -0
- package/package.json +50 -0
- package/powerhouse-docs@0.0.0 +0 -0
- package/sidebars.ts +33 -0
- package/src/components/HomepageFeatures/index.tsx +250 -0
- package/src/components/HomepageFeatures/styles.module.css +267 -0
- package/src/css/custom.css +450 -0
- package/src/pages/index.module.css +37 -0
- package/src/pages/index.tsx +42 -0
- package/src/pages/markdown-page.md +7 -0
- package/static/.nojekyll +0 -0
- package/static/fonts/FranieBold.otf +0 -0
- package/static/fonts/FranieRegular.otf +0 -0
- package/static/img/Powerhouse Website Drive.png +0 -0
- package/static/img/Powerhouse Website Storage Layer (1).png +0 -0
- package/static/img/Powerhouse Website Storage Layer.png +0 -0
- package/static/img/Powerhouse-main-light.svg +13 -0
- package/static/img/Powerhouse-main.svg +13 -0
- package/static/img/Renown Intro Diagram.png +0 -0
- package/static/img/Union.svg +3 -0
- package/static/img/academy/icons/Advanced.svg +4 -0
- package/static/img/academy/icons/Book.svg +5 -0
- package/static/img/academy/icons/Cookbook.svg +3 -0
- package/static/img/academy/icons/Create.svg +3 -0
- package/static/img/academy/icons/Data.svg +3 -0
- package/static/img/academy/icons/Editor.svg +3 -0
- package/static/img/academy/icons/Flash.svg +3 -0
- package/static/img/academy/icons/Launch.svg +3 -0
- package/static/img/academy-icon.png +0 -0
- package/static/img/connect-icon.png +0 -0
- package/static/img/connect.png +0 -0
- package/static/img/docusaurus-social-card.jpg +0 -0
- package/static/img/docusaurus.png +0 -0
- package/static/img/empty-background.png +0 -0
- package/static/img/favicon.ico +0 -0
- package/static/img/fusion-icon.png +0 -0
- package/static/img/fusion.png +0 -0
- package/static/img/ph-icon-light.svg +3 -0
- package/static/img/powerhouse-layer.png +0 -0
- package/static/img/powerhouse-storage-layer.png +0 -0
- package/static/img/reactor.png +0 -0
- package/static/img/renown-icon.png +0 -0
- package/static/img/renown.png +0 -0
- package/static/img/switchboard-icon.png +0 -0
- package/static/img/switchboard.png +0 -0
- package/static/img/undraw_docusaurus_mountain.svg +171 -0
- package/static/img/undraw_docusaurus_react.svg +170 -0
- package/static/img/undraw_docusaurus_tree.svg +40 -0
- package/static/img/video-placeholder.svg +16 -0
- package/static.json +7 -0
- package/tsconfig.json +7 -0
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
---
|
|
2
|
+
sidebar_position: 0
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# Getting Started
|
|
6
|
+
|
|
7
|
+
## Introduction
|
|
8
|
+
|
|
9
|
+
Welcome to the Powerhouse Analytics Engine Documentation. This engine is a powerful time-series analytics system, written in Typescript with an optional GraphQL interface on top. It is designed to run _anywhere_. Browsers, server environments, CLI tools, etc.
|
|
10
|
+
|
|
11
|
+
This documentation serves as a guide for Typescript and GraphQL API usage, not library development. For documentation on how to build or contribute to this project, see our [README](https://github.com/powerhouse-inc/analytics-engine/blob/main/README.md).
|
|
12
|
+
|
|
13
|
+
## Overview
|
|
14
|
+
|
|
15
|
+

|
|
16
|
+
|
|
17
|
+
This system can be broken up into several major systems: **queries**, **engine components** (including filters and aggregation), and **storage**. Each of these systems have detailed documentation, but it is most helpful to start with a holistic understanding of the major pieces of data: series and dimensions.
|
|
18
|
+
|
|
19
|
+
### Series and Dimensions
|
|
20
|
+
|
|
21
|
+
All metrics are collected and stored using only two objects: **AnalyticsSeries** and **AnalyticsDimension**. To be a successful consumer of this system, It is vital to understand the intent of these two pieces of data.
|
|
22
|
+
|
|
23
|
+

|
|
24
|
+
|
|
25
|
+
Typical time-series databases (like [Graphite](https://graphiteapp.org/)) usually store data as `(time, value)` tuples: relating an explicit value to an explicit time. In the Web3 space, we have learned through [real uses cases with MakerDAO](https://fusion.sky.money/), that this isn't quite flexible enough for Web3 constructs.
|
|
26
|
+
|
|
27
|
+
Instead, the time series values in this system are given by a value paired with a _time interval_. That is, values have a start and an end. This allows for interpolation functions across a time interval, like a linear change.
|
|
28
|
+
|
|
29
|
+
Of course, the default is simply a constant function, giving a constant value inside of the time interval.
|
|
30
|
+
|
|
31
|
+

|
|
32
|
+
|
|
33
|
+
This means that when we want to query data, we are querying over intervals and can query for either "running totals" or changes (deltas) over an interval.
|
|
34
|
+
|
|
35
|
+
In the example above, querying over different intervals returns different totals and deltas depending on interval. This is another difference with typical time-series databases: deltas as first class. The analytics system takes care of this aggregation for users.
|
|
36
|
+
|
|
37
|
+

|
|
38
|
+
|
|
39
|
+
In real use cases, we generally have many data sets running in parallel. In this example, we have two related systems, both inserting data. Some of the intervals overlap, some don't, and we see some gaps in the data as well.
|
|
40
|
+
|
|
41
|
+

|
|
42
|
+
|
|
43
|
+
The question is: how do we query and aggregate this related data? We could potentially make multiple queries and do this ourselves, but it would require a good understanding of the implementation details.
|
|
44
|
+
|
|
45
|
+
This is where the **AnalyticsDimension** object comes into play.
|
|
46
|
+
|
|
47
|
+

|
|
48
|
+
|
|
49
|
+
While there will be thousands or millions of series objects, there will be far fewer referenced dimension objects. Dimensions allow users to decorate series objects with query and aggregation information. These objects are shared across all the series records.
|
|
50
|
+
|
|
51
|
+
Visually, you can think of series as values running up and down, while dimensions allow for query and aggregation _across_ the different series objects.
|
|
52
|
+
|
|
53
|
+

|
|
54
|
+
|
|
55
|
+
### Structure v Reporting
|
|
56
|
+
|
|
57
|
+
The Series and Dimensions objects give control over how metric data is defined and related. That is, they define the **structure** of data. Three new concepts: Paths, LODs, and granularity, are parameters used to define how the system should _report_ the data.
|
|
58
|
+
|
|
59
|
+
### Paths
|
|
60
|
+
|
|
61
|
+
Let's start with paths. A `path` is a field on an `AnalyticsDimension` that describes the _specificity of the query_.
|
|
62
|
+
|
|
63
|
+

|
|
64
|
+
|
|
65
|
+
In this example, inspired by real MakerDAO metrics, we have a number of series values that have been decorated with a couple of dimensions. Dimensions, as we have discussed, have a name like "budget". They also have this `path` field, which looks like a URI or directory hierarchy: a string made up of smaller strings separated by `/`s.
|
|
66
|
+
|
|
67
|
+
Note that, some series values have been decorated with multiple dimensions, some with a single dimension, and some have not been decorated with either.
|
|
68
|
+
|
|
69
|
+
It's clear enough that when we request budget data for a specific MIP dimension, like the purple one (`/atlas/mip40/MIP40c3SP61`), we should be getting totals and deltas across only the purple series objects.
|
|
70
|
+
|
|
71
|
+
But what if we want to query across everything related to `mip40`?
|
|
72
|
+
|
|
73
|
+

|
|
74
|
+
|
|
75
|
+
This is the beauty of dimension paths. All we need to do is use a `path` to describe the _specificity_ of the query. In this case, we use `/atlas/mip40`, which will match all dimensions that start with `/atlas/mip40`, which in turn matches all series values related to to those dimensions.
|
|
76
|
+
|
|
77
|
+
### LODs
|
|
78
|
+
|
|
79
|
+
`LOD` is short for "level of detail", and it is not a field found on either the series or dimension objects. It is a parameter used in queries only, to determine how results are aggregated across dimensions.
|
|
80
|
+
|
|
81
|
+
Let's look at our example again.
|
|
82
|
+
|
|
83
|
+

|
|
84
|
+
|
|
85
|
+
Here we have asked for a report on the `budget` metric, with path `/atlast/mip40`. The path determines the specificity of the query, and LOD determines how to aggregate results.
|
|
86
|
+
|
|
87
|
+

|
|
88
|
+
|
|
89
|
+
An LOD of `0` will group all results together in a single value. An LOD of `1` will group results by the first part of the path, in this case: `/atlas`. And so on for an LOD of 2 or 3. This means that increasing the LOD will increase the number of values retieved as the system needs to aggregate with higher specificity.
|
|
90
|
+
|
|
91
|
+
An LOD greater than the number of parts of the path (in this case, 4 or more) will append a `/*` for each additional path part to the end of the grouped results but will provide the same results as the highest level of detail.
|
|
92
|
+
|
|
93
|
+
### Granularity
|
|
94
|
+
|
|
95
|
+
While LODs allow you to aggregate series data across dimensions, _granularity_ refers to how data is aggregated over time. It determines the time span each data point or record covers. Choosing the right granularity is crucial for meaningful analysis, as it affects the interpretation and insights that can be drawn from the data.
|
|
96
|
+
|
|
97
|
+
#### Available Options
|
|
98
|
+
|
|
99
|
+
The analytics engine supports various granularity options, each suitable for different types of analysis:
|
|
100
|
+
|
|
101
|
+
1. `total`: Provides a cumulative view of data over the entire time frame available in the dataset. Best used for overall summaries and long-term analysis.
|
|
102
|
+
|
|
103
|
+
2. `annual`: Aggregates data on a yearly basis. Ideal for year-over-year comparisons and annual trend analysis.
|
|
104
|
+
|
|
105
|
+
3. `semiAnnual` : Breaks down data into six-month periods. Useful for understanding bi-annual trends and patterns.
|
|
106
|
+
|
|
107
|
+
4. `quarterly`: Divides data into quarters, offering insights into seasonal trends or quarter-over-quarter performance.
|
|
108
|
+
|
|
109
|
+
5. `monthly` : Monthly granularity is useful for a more detailed view of trends and patterns, particularly useful for operational planning and monitoring.
|
|
110
|
+
|
|
111
|
+
6. `weekly` : Provides weekly data aggregation, which is helpful for short-term performance tracking and operational adjustments.
|
|
112
|
+
daily: Offers a day-to-day breakdown, ideal for detailed analysis of daily operations or events.
|
|
113
|
+
|
|
114
|
+
7. `hourly`: The most granular level, providing insights into hourly fluctuations. Useful in scenarios where short-term data spikes or dips are significant.
|
|
115
|
+
|
|
116
|
+
#### How Granularity Affects Query Results
|
|
117
|
+
|
|
118
|
+
- Data Volume: Higher granularity (like hourly or daily) results in a larger volume of data points, providing more detailed insights but potentially leading to more complex analysis. Lower granularity (like annual or total) simplifies the data into fewer, broader data points.
|
|
119
|
+
|
|
120
|
+
- Trend Analysis: Finer granularity helps in identifying short-term trends and anomalies, whereas coarser granularity is better for long-term trend analysis and strategic planning.
|
|
121
|
+
Performance Impact: Queries with finer granularity might be more resource-intensive and take longer to execute due to the larger number of data points processed.
|
|
122
|
+
|
|
123
|
+
- Contextual Relevance: The choice of granularity should match the context of the analysis. For instance, financial planning might prefer annual or quarterly granularity, while operational monitoring might require daily or hourly data.
|
|
124
|
+
|
|
125
|
+
- Comparative Analysis: Different granularity levels can be used for comparative analysis, such as comparing detailed daily data (through daily granularity) against broader monthly trends (using monthly granularity) to understand day-to-day variations within the context of a monthly overview.
|
|
126
|
+
|
|
127
|
+
In summary, the choice of granularity in your query significantly influences the scope, detail, and utility of the analytics results. It is important to align the granularity with the specific analytical objectives and the nature of the data being analyzed to ensure that the insights derived are both relevant and actionable.
|
|
128
|
+
|
|
129
|
+
## Cheatsheet
|
|
130
|
+
|
|
131
|
+
These are high-level definitions of major terms you will need to know.
|
|
132
|
+
|
|
133
|
+
* **AnalyticsSeries** - The individual metric values, comprised of `(value, start, end)`. There should be one of these for every change in value.
|
|
134
|
+
|
|
135
|
+
* **AnalyticsDimension** - Shared objects that decorate series objects with `name` and `path` information. Used to relate metrics to one another.
|
|
136
|
+
|
|
137
|
+
* **Path** - Composable, `/`-delimited string field on dimension that is used to _determine specificity_ during queries.
|
|
138
|
+
|
|
139
|
+
* **LOD** - A number that, when paired with `path`, determines how to aggregate values across dimensions.
|
|
140
|
+
|
|
141
|
+
* **Granularity** - Determines how to aggregate values over time, like "monthly" or "yearly".
|
|
142
|
+
|
|
143
|
+
## Next Steps
|
|
144
|
+
|
|
145
|
+
Next steps depend on use case.
|
|
146
|
+
|
|
147
|
+
* For developers looking to **input data**, or **query data with Typescript**, see the [Typescript](./typescript/) docs.
|
|
148
|
+
* For developers interested in **querying data with GraphQL**, see the [GraphQL](./graphql/) documentation. Data cannot be input through GraphQL.
|
|
149
|
+
* For developers interested in **contributing to this project**, see the developer documentation [on GitHub](https://github.com/powerhouse-inc/analytics-engine).
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
---
|
|
2
|
+
sidebar_position: 102
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# Benchmarks
|
|
6
|
+
|
|
7
|
+
Due to the performance-sensitive nature of metrics collection, we include a [suite of benchmarks](https://github.com/powerhouse-inc/analytics-engine/tree/main/benchmarks) that compare the various analytics store implementations. These are run every publish and stored in the associated [GitHub action history](https://github.com/powerhouse-inc/analytics-engine/actions/runs/12123429624/job/33798972072).
|
|
8
|
+
|
|
9
|
+
We also include results from major releases [here](https://github.com/powerhouse-inc/analytics-engine/tree/main/benchmarks/results).
|
|
10
|
+
|
|
11
|
+
In summary, we have found that the `MemoryAnalyticsStore` and `PostgresAnalyticsStore` implementations are very close in performance, with the Postgres implementation slightly faster in most applications.
|
|
12
|
+
|
|
13
|
+
## Query-list
|
|
14
|
+
|
|
15
|
+
In [this benchmark](https://github.com/powerhouse-inc/analytics-engine/blob/main/benchmarks/results/query-list/), we ran the most frequent 600+ real-world queries against matching tables of around 200k records.
|
|
16
|
+
|
|
17
|
+
We found that, on average, the `MemoryAnalyticsStore` implementation took about `1.36x` the time it took `PostgresAnalyticsStore`. Full results [here](https://github.com/powerhouse-inc/analytics-engine/blob/main/benchmarks/results/query-list/pglite.txt).
|
|
18
|
+
|
|
19
|
+
## WASM
|
|
20
|
+
|
|
21
|
+
In [this benchmark](https://github.com/powerhouse-inc/analytics-engine/blob/main/benchmarks/results/wasm/), we analyze the startup and insert time for the `MemoryAnalyticsStore` implementation.
|
|
22
|
+
|
|
23
|
+
| Operation | Ops per Sec | Average Time |
|
|
24
|
+
| ------------ | ----------- | ------------ |
|
|
25
|
+
| Init | 3 | 323.00 ms |
|
|
26
|
+
| 100 Inserts | 478 | 2.08 ms |
|
|
27
|
+
| 200k Inserts | 0 | 4448.10 ms |
|
package/docs/academy/02-AdvancedTutorial/04-WorkWithData/06-Analytics Engine/typescript/browser.md
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
---
|
|
2
|
+
sidebar_position: 2
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# Browser Store
|
|
6
|
+
|
|
7
|
+
The `BrowserAnalyticsStore` is an `IAnalyticsStore` implementation that sits on top of [`MemoryAnalyticsStore`](#memory) but adds an `IndexedDB` plugin for persistence.
|
|
8
|
+
|
|
9
|
+
<aside class="notice">
|
|
10
|
+
See the <a href="#compatibility">Compatibility</a> section for details on which stores are intended to be used in different execution environments.
|
|
11
|
+
</aside>
|
|
12
|
+
|
|
13
|
+
## Construction
|
|
14
|
+
|
|
15
|
+
A default implementation of the `BrowserAnalyticsStore` may be created with no arguments, or options are provided for specialized needs.
|
|
16
|
+
|
|
17
|
+
```typescript
|
|
18
|
+
// creates a database named "analytics"
|
|
19
|
+
const store = new BrowserAnalyticsStore();
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
> Create with a specific database name.
|
|
23
|
+
|
|
24
|
+
```typescript
|
|
25
|
+
const store = new BrowserAnalyticsStore({ databaseName: "analytics" });
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
> It may also be created with optional contructor arguments that may be helpful for debugging or metrics collection.
|
|
29
|
+
|
|
30
|
+
```typescript
|
|
31
|
+
const store = new BrowserAnalyticsStore({
|
|
32
|
+
databaseName: "analytics",
|
|
33
|
+
|
|
34
|
+
queryLogger: defaultQueryLogger("browser"),
|
|
35
|
+
resultsLogger: defaultResultsLogger("browser"),
|
|
36
|
+
profiler: new PassthroughAnalyticsProfiler(),
|
|
37
|
+
});
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
For more details on these optional constructor parameters, see the [Utilities](#utilities) section.
|
|
41
|
+
|
|
42
|
+
Since the constructor options argument extends the `MemoryAnalyticsStore` options argument, see the [`MemoryAnalyticsStore`](#memory) documentation for further details on other optional parameters.
|
|
43
|
+
|
|
44
|
+
## Initialization
|
|
45
|
+
|
|
46
|
+
Similar to the [`MemoryAnalyticsStore`](#memory), this implementation requires an asynchronous initialization step.
|
|
47
|
+
|
|
48
|
+
> Note that this method is not available on the `IAnalyticsStore` interface, but only on the concrete type.
|
|
49
|
+
|
|
50
|
+
```typescript
|
|
51
|
+
// create the store
|
|
52
|
+
const store = new BrowserAnalyticsStore();
|
|
53
|
+
|
|
54
|
+
// initialize it
|
|
55
|
+
await store.init();
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Persistence
|
|
59
|
+
|
|
60
|
+
The `databaseName` constructor argument namespaces the database. This allows users to create multiple stores, if needed, which will not conflict with each other. You can use your browser's developer tools to see these databases, usually through the "Storage" tab.
|
|
61
|
+
|
|
62
|
+
<aside class="notice">
|
|
63
|
+
While manipulating the data manually is not recommended, this allows you to easily delete and recreate databases if needed.
|
|
64
|
+
</aside>
|
|
65
|
+
|
|
66
|
+

|
|
67
|
+
|
|
68
|
+
The store interface is intended to be immutable, meaning that it does not provide a general method of wiping a DB. However, an IDB database may be deleted via the standard [IDB API](https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API).
|
|
69
|
+
|
|
70
|
+
```typescript
|
|
71
|
+
// creates the database
|
|
72
|
+
const store = new BrowserAnalyticsStore({ databaseName: "my-analytics" });
|
|
73
|
+
await store.init();
|
|
74
|
+
|
|
75
|
+
// use the browser API to delete the database
|
|
76
|
+
window.indexedDB.deleteDatabase("my-analytics");
|
|
77
|
+
```
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
---
|
|
2
|
+
sidebar_position: 101
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# Compatibility
|
|
6
|
+
|
|
7
|
+
This guide is intended for making high level decisions about which storage systems to use in which contexts. These stores have not been tested across thousands of browser or serverside runtime versions.
|
|
8
|
+
|
|
9
|
+
| Store | Browser | Node | Bun |
|
|
10
|
+
| ------------------------ | ------- | ---- | --- |
|
|
11
|
+
| `MemoryAnalyticsStore` | X | X | X |
|
|
12
|
+
| `BrowserAnalyticsStore` | X | | |
|
|
13
|
+
| `KnexAnalyticsStore` | X | X | X |
|
|
14
|
+
| `PostgresAnalyticsStore` | | X | X |
|
package/docs/academy/02-AdvancedTutorial/04-WorkWithData/06-Analytics Engine/typescript/index.md
ADDED
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
---
|
|
2
|
+
sidebar_position: 2
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# Typescript API
|
|
6
|
+
|
|
7
|
+
## Overview
|
|
8
|
+
|
|
9
|
+
The analytics system is broken into several modules, allowing developers to deploy across many environments, for a diverse set of use cases.
|
|
10
|
+
|
|
11
|
+

|
|
12
|
+
|
|
13
|
+
The `core` library contains common data types and abstractions used throughout. The job of the core library is to link together query, storage, and aggregation logic.
|
|
14
|
+
|
|
15
|
+

|
|
16
|
+
|
|
17
|
+
The `knex`, `pg`, and `browser` libraries contain various storage implementations. Finally, the `graphql` library contains types, resolvers, and data types for a GraphQL API on top.
|
|
18
|
+
|
|
19
|
+
## Querying Data
|
|
20
|
+
|
|
21
|
+
The entry point for data queries in Typescript is the `AnalyticsQueryEngine`. This wraps an `IAnalyticsStore` implementation, which will be discussed in detail later.
|
|
22
|
+
|
|
23
|
+
In this example, we create a simple in-memory storage engine, compatible with all platforms.
|
|
24
|
+
|
|
25
|
+
```typescript
|
|
26
|
+
import { AnalyticsQueryEngine } from "@powerhousedao/analytics-core";
|
|
27
|
+
import { MemoryAnalyticsStore } from "@powerhousedao/analytics-memory";
|
|
28
|
+
|
|
29
|
+
const engine = new AnalyticsQueryEngine(new MemoryAnalyticsStore());
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
### AnalyticsQuery
|
|
33
|
+
|
|
34
|
+
Queries use the engine's `execute` function, taking an `AnalyticsQuery` parameter:
|
|
35
|
+
|
|
36
|
+
```typescript
|
|
37
|
+
type AnalyticsQuery = {
|
|
38
|
+
start: DateTime | null;
|
|
39
|
+
end: DateTime | null;
|
|
40
|
+
metrics: string[];
|
|
41
|
+
currency: AnalyticsPath;
|
|
42
|
+
select: Record<string, AnalyticsPath[]>;
|
|
43
|
+
granularity: AnalyticsGranularity;
|
|
44
|
+
lod: Record<string, number | null>;
|
|
45
|
+
};
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Each field plays a specific role in determining what data is returned by the analytics engine. Here's a clear and concise description of each:
|
|
49
|
+
|
|
50
|
+
- `start`: This is the starting date and time of the series, using Luxon types. See the [note about times](#a-note-about-times) for more information.
|
|
51
|
+
|
|
52
|
+
- `end`: This is the ending date and time of the series, using Luxon types. See the [note about times](#a-note-about-times) for more information.
|
|
53
|
+
|
|
54
|
+
- `metrics`: Specifies the particular metrics being queried (e.g., `"budget"`). Each element will refer to the `name` field on the related [`AnalyticsDimension` objects](../intro.md#series-and-dimensions).
|
|
55
|
+
|
|
56
|
+
- `currency`: A path reference to the currency to match (e.g. - `"usd"`).
|
|
57
|
+
|
|
58
|
+
- `select`: This object is a map from metric name to a list of paths (e.g. - `{ budget: ["/atlas"] }`). These paths are used for matching via the [`AnalyticsDimension` objects](../intro.md#series-and-dimensions).
|
|
59
|
+
|
|
60
|
+
- `granularity`: This field denotes the aggregation period for the data (e.g., `"monthly"`). A full list of options is available [here](../intro.md#granularity).
|
|
61
|
+
|
|
62
|
+
- `lod`: A map from metric name to the level of detail parameter (e.g. - `3`), described in detail [here](../intro.md#lods).
|
|
63
|
+
|
|
64
|
+
This query structure allows users to extract detailed and summarized analytics data. A full example will look like:
|
|
65
|
+
|
|
66
|
+
```typescript
|
|
67
|
+
const results = await engine.execute({
|
|
68
|
+
start: DateTime.fromIso("2020-01-01T00:00:00.000Z"),
|
|
69
|
+
end: "2100-01-01T00:00:00.000Z",
|
|
70
|
+
currency: AnalyticsPath.fromArray(["DAI"]),
|
|
71
|
+
metrics: ["Forecast"],
|
|
72
|
+
select: {
|
|
73
|
+
report: [AnalyticsPath.fromArray(["atlas", "EcosystemActor", "50", "2023", "11"])],
|
|
74
|
+
},
|
|
75
|
+
granularity: 0,
|
|
76
|
+
lod: {},
|
|
77
|
+
});
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
This will return an array of `GroupedPeriodResult`.
|
|
81
|
+
|
|
82
|
+
### GroupedPeriodResult
|
|
83
|
+
|
|
84
|
+
This object is an aggregate. It represents a combined group of metrics over a time period.
|
|
85
|
+
|
|
86
|
+
```typescript
|
|
87
|
+
type GroupedPeriodResult = {
|
|
88
|
+
period: string;
|
|
89
|
+
start: DateTime;
|
|
90
|
+
end: DateTime;
|
|
91
|
+
rows: Array<{
|
|
92
|
+
dimensions: Record<string, AnalyticsDimension>;
|
|
93
|
+
metric: string;
|
|
94
|
+
unit: string | null;
|
|
95
|
+
value: number;
|
|
96
|
+
sum: number;
|
|
97
|
+
}>;
|
|
98
|
+
};
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
- `period`: Within each series, this field denotes the aggregation period for the data (e.g., `"monthly"`), with a short description of which month, year, quarter, etc.
|
|
102
|
+
|
|
103
|
+
- `start`: This is the starting date and time of the series, using Luxon types. See the [note about times](#a-note-about-times) for more information.
|
|
104
|
+
|
|
105
|
+
- `end`: This is the ending date and time of the series, using Luxon types. See the [note about times](#a-note-about-times) for more information.
|
|
106
|
+
|
|
107
|
+
- `rows`: Represents the individual records or entries in the data series. Each row corresponds to a unique combination of dimensions for the specified period.
|
|
108
|
+
|
|
109
|
+
- `metric`: Within each row, this field specifies the particular metric being measured (e.g., `"budget"`).
|
|
110
|
+
|
|
111
|
+
- `unit`: This indicates the unit of measurement for the metric, such as quantities, currency (e.g., `"DAI"`), or percentages.
|
|
112
|
+
|
|
113
|
+
- `dimensions`: A nested array that provides context for the metric by breaking it down into finer categories or segments, such as `"project"` or `"category"`. Each dimension can contain:
|
|
114
|
+
- `name`: The identifier or key for the dimension.
|
|
115
|
+
- `path`: A structured representation of the dimension's hierarchy or location within a dataset.
|
|
116
|
+
- `label`: A human-readable label for the dimension, which can be used for display purposes.
|
|
117
|
+
- `description`: A brief explanation of the dimension to give users an understanding of what it represents.
|
|
118
|
+
- `icon`: A graphical representation or icon associated with the dimension for easier identification in user interfaces.
|
|
119
|
+
|
|
120
|
+
- `value`: The actual numerical value of the metric for each row within the specified period.
|
|
121
|
+
|
|
122
|
+
- `sum`: A total or aggregated value of the metric over the entire period, providing a summarized figure.
|
|
123
|
+
|
|
124
|
+
### A Note about Times
|
|
125
|
+
|
|
126
|
+
Time is a bit of a sore subject in JavaScript, and we wrestled a bit with how to attack this problem.
|
|
127
|
+
|
|
128
|
+
First, we started with the builtin `Date` type. Unfortunately, `Date` objects are local-time objects. That means that `new Date('2024-12-25')` might mean one time on this machine and another time on someone elses. While `Date` objects _can_ be created using UTC (`new Date('2024-012-25T14:46:22.001Z')`), the resulting `Date` object is still in local time. While `toISO()` does exist, which will output a UTC timestamp, there is no way to tell whether or not the `Date` _was created_ with a UTC timestamp and actually creating the time object the user thought they were creating. Imagining calling the `Date` constructor and ending up with a time different than the one you thought you passed in. Eek.
|
|
129
|
+
|
|
130
|
+
We then considered strings, but those suffer from a load of other problems and they are only really going to be runtime checked.
|
|
131
|
+
|
|
132
|
+
As this is a time-series analytics system, times are rather important to get right, so we thought we'd stick to a data type that has time, date, and timezone information all in one object. Enter the [`luxon`](https://moment.github.io/luxon/#/) library. The luxon library had exactly the data object we were looking for. It is difficult (but not impossible) to create a luxon `DateTime` object with a timezone you weren't intending to use.
|
|
133
|
+
|
|
134
|
+
This is why our analytics API uses these luxon types as inputs and outputs to the system: to make it hard to screw up.
|
|
135
|
+
|
|
136
|
+
## Inserting Data
|
|
137
|
+
|
|
138
|
+
The `IAnalyticsStore` interface is the primary entry point for inserting and deleting data. Multiple storage implementations are provided, but for simplicity we can get up and running quickly with the [`MemoryAnalyticsStore`](#memory).
|
|
139
|
+
|
|
140
|
+
```typescript
|
|
141
|
+
import { MemoryAnalyticsStore } from "@powerhousedao/analytics-engine-memory";
|
|
142
|
+
|
|
143
|
+
const store = new MemoryAnalyticsStore();
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
Data can be added using the `addSeriesValue` method.
|
|
147
|
+
|
|
148
|
+
```typescript
|
|
149
|
+
import { DateTime } from "luxon";
|
|
150
|
+
import { AnalyticsPath } from "@powerhousedao/analytics-engine-core";
|
|
151
|
+
|
|
152
|
+
const source = AnalyticsPath.fromString("example/insert");
|
|
153
|
+
await store.addSeriesValue([
|
|
154
|
+
{
|
|
155
|
+
start: DateTime.utc(2021, 1, 1),
|
|
156
|
+
source,
|
|
157
|
+
value: 10000,
|
|
158
|
+
unit: "DAI",
|
|
159
|
+
metric: "budget",
|
|
160
|
+
dimensions: {
|
|
161
|
+
budget: AnalyticsPath.fromString("atlas/legacy/core-units/PE-001"),
|
|
162
|
+
category: AnalyticsPath.fromString(
|
|
163
|
+
"atlas/headcount/CompensationAndBenefits/FrontEndEngineering"
|
|
164
|
+
),
|
|
165
|
+
project: source,
|
|
166
|
+
},
|
|
167
|
+
},
|
|
168
|
+
]);
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
## Subscribing to Data Changes
|
|
172
|
+
|
|
173
|
+
The `IAnalyticsStore` also provides an API for subscribing to data changes. This is achieved by subscribing to an `AnalyticsPath`.
|
|
174
|
+
|
|
175
|
+
```typescript
|
|
176
|
+
const store = new MemoryAnalyticsStore();
|
|
177
|
+
|
|
178
|
+
// subscribe
|
|
179
|
+
const unsub = store.subscribeToSource(AnalyticsPath.fromString("atlas/"), (source) => {
|
|
180
|
+
console.log('Atlas data was changed!');
|
|
181
|
+
|
|
182
|
+
// decide whether or not to requery
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
// elided
|
|
186
|
+
|
|
187
|
+
// remove your subscription
|
|
188
|
+
unsub();
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
The subscription API provides many of the same guarantees regarding pathing that the rest of the analytics system does.
|
|
192
|
+
|
|
193
|
+
```typescript
|
|
194
|
+
store.subscribeToSource(AnalyticsPath.fromString("atlas/"), handler);
|
|
195
|
+
|
|
196
|
+
// this will trigger the handler
|
|
197
|
+
await store.addSeriesValue({ source: AnalyticsPath.fromString("atlas/foo"), ... });
|
|
198
|
+
|
|
199
|
+
// this will trigger the handler
|
|
200
|
+
await store.addSeriesValue({ source: AnalyticsPath.fromString("atlas/foo/bar"), ... });
|
|
201
|
+
|
|
202
|
+
// this will NOT trigger the handler
|
|
203
|
+
await store.addSeriesValue({ source: AnalyticsPath.fromString("rwa/foo"), ... });
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
Wildcards can also be used at sub-levels of the path.
|
|
207
|
+
|
|
208
|
+
```typescript
|
|
209
|
+
store.subscribeToSource(AnalyticsPath.fromString("atlas/*/test"), handler);
|
|
210
|
+
|
|
211
|
+
// this will trigger the handler
|
|
212
|
+
await store.addSeriesValue({ source: AnalyticsPath.fromString("atlas/foo/test"), ... });
|
|
213
|
+
|
|
214
|
+
// this will trigger the handler
|
|
215
|
+
await store.addSeriesValue({ source: AnalyticsPath.fromString("atlas/another-thing/test"), ... });
|
|
216
|
+
|
|
217
|
+
// this will trigger the handler
|
|
218
|
+
await store.addSeriesValue({ source: AnalyticsPath.fromString("atlas/foo/test/a/b/c"), ... });
|
|
219
|
+
|
|
220
|
+
// this will NOT trigger the handler
|
|
221
|
+
await store.addSeriesValue({ source: AnalyticsPath.fromString("rwa/foo/test"), ... });
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
## Store Implementations
|
|
225
|
+
|
|
226
|
+
Multiple storage implementations are provided, each with comprehensive documentation. See the corresponding docs for:
|
|
227
|
+
|
|
228
|
+
- [MemoryAnalyticsStore](#memory)
|
|
229
|
+
- [BrowserAnalyticsStore](#browser)
|
|
230
|
+
- [PostgresAnalyticsStore](#postgres)
|
package/docs/academy/02-AdvancedTutorial/04-WorkWithData/06-Analytics Engine/typescript/memory.md
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
---
|
|
2
|
+
sidebar_position: 1
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# Memory Store
|
|
6
|
+
|
|
7
|
+
The `MemoryAnalyticsStore` is an `IAnalyticsStore` implementation that uses a an in-memory database as its storage mechanism. Under the hood, we load a WASM build of Postgres, called [PGlite](https://pglite.dev/).
|
|
8
|
+
|
|
9
|
+
<aside class="notice">
|
|
10
|
+
See the <a href="#compatibility">Compatibility</a> section for details on which stores are intended to be used in different execution environments.
|
|
11
|
+
</aside>
|
|
12
|
+
|
|
13
|
+
## Construction
|
|
14
|
+
|
|
15
|
+
The `MemoryAnalyticsStore` is simple to create.
|
|
16
|
+
|
|
17
|
+
> Create with no arguments.
|
|
18
|
+
|
|
19
|
+
```typescript
|
|
20
|
+
const store = new MemoryAnalyticsStore();
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
> The `MemoryAnalyticsStore` may also be created with optional contructor arguments that may be helpful for debugging or metrics collection.
|
|
24
|
+
|
|
25
|
+
```typescript
|
|
26
|
+
const store = new MemoryAnalyticsStore({
|
|
27
|
+
queryLogger: querydefaultQueryLogger("memory"),
|
|
28
|
+
resultsLogger: defaultResultsLogger("memory"),
|
|
29
|
+
profiler: new PassthroughAnalyticsProfiler(),
|
|
30
|
+
});
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
For more details on these optional constructor parameters, see the [Utilities](#utilities) section.
|
|
34
|
+
|
|
35
|
+
> Additionally, both `knex` and `pglite` objects may be passed in. This is helpful in contexts where multiple objects are sharing the same database.
|
|
36
|
+
|
|
37
|
+
```typescript
|
|
38
|
+
// knex must be created with these options
|
|
39
|
+
const knex = knexFactory({ client: "pg", useNullAsDefault: true });
|
|
40
|
+
|
|
41
|
+
// create your own Pglite instance and pass it in
|
|
42
|
+
// See (https://github.com/electric-sql/pglite/blob/main/packages/pglite/src/interface.ts) for full list of options.
|
|
43
|
+
const pgLiteFactory = () => PGlite.create({
|
|
44
|
+
debug: 3,
|
|
45
|
+
relaxedDurability: false,
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
const store = new MemoryAnalyticsStore({
|
|
49
|
+
knex,
|
|
50
|
+
pgLiteFactory,
|
|
51
|
+
})
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Initialization
|
|
55
|
+
|
|
56
|
+
While easy to use, the `MemoryAnalyticsStore` requires an asynchronous initialization step. This is for two reasons.
|
|
57
|
+
|
|
58
|
+
In cases where the `MemoryAnalyticsStore` was not provided a `PGlite` instance, it needs time to download and initialize the WASM build.
|
|
59
|
+
|
|
60
|
+
Additionally, it also needs to initialize the database schema of the in-memory database. This is distinct from the <a href="#postgres">Postgres implementation</a>, which assumes a fully-initialized Postgres database already exists. The initialization is idempotent, so tables already created will not be recreated.
|
|
61
|
+
|
|
62
|
+
The full SQL query used can be found in the [`MemoryAnalyticsStore` source](https://github.com/powerhouse-inc/analytics-engine/blob/main/browser/src/MemoryAnalyticsStore.ts).
|
|
63
|
+
|
|
64
|
+
> Note that this method is not available on the `IAnalyticsStore` interface, but only on the `MemoryAnalyticsStore` type.
|
|
65
|
+
|
|
66
|
+
```typescript
|
|
67
|
+
// create the store
|
|
68
|
+
const store = new MemoryAnalyticsStore();
|
|
69
|
+
|
|
70
|
+
// initialize it
|
|
71
|
+
await store.init();
|
|
72
|
+
```
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
---
|
|
2
|
+
sidebar_position: 3
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# Postgres Store
|
|
6
|
+
|
|
7
|
+
The `PostgresAnalyticsStore` is an `IAnalyticsStore` implementation that leverages a Postgres database. It requires some APIs that do not run in a browser, and is intended for server-side applications.
|
|
8
|
+
|
|
9
|
+
<aside class="notice">
|
|
10
|
+
See the <a href="#compatibility">Compatibility</a> section for details on which stores are intended to be used in different execution environments.
|
|
11
|
+
</aside>
|
|
12
|
+
|
|
13
|
+
## Construction
|
|
14
|
+
|
|
15
|
+
The `PostgresAnalyticsStore` uses the [`pg`](https://www.npmjs.com/package/pg) package and requires Postgres connection information.
|
|
16
|
+
|
|
17
|
+
By providing a PG connection string, the store will automatically create a `pg` instance, internally.
|
|
18
|
+
|
|
19
|
+
A docker-compose file is provided [here](https://github.com/powerhouse-inc/analytics-engine/blob/main/pg/docker-compose.test.yml) that will spin up an instance quickly. Note that it cannot be copy/pasted but must be run in the checked out repository to access the correct initialization scripts. See the [developer documentation](https://github.com/powerhouse-inc/analytics-engine/tree/main?tab=readme-ov-file#pg) for more information.
|
|
20
|
+
|
|
21
|
+
> Create with only a connection string.
|
|
22
|
+
|
|
23
|
+
```typescript
|
|
24
|
+
// connects to a local postgres instance, configured by the provided docker-compose file
|
|
25
|
+
const store = new PostgresAnalyticsStore({
|
|
26
|
+
connectionString: "postgresql://postgres:password@localhost:5555/analytics",
|
|
27
|
+
});
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
> Instead, create with a `knex` object.
|
|
31
|
+
|
|
32
|
+
```typescript
|
|
33
|
+
import knexFactory from "knex";
|
|
34
|
+
|
|
35
|
+
const knex = knexFactory({
|
|
36
|
+
client: "pg",
|
|
37
|
+
connection: "...",
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
const store = new PostgresAnalyticsStore({
|
|
41
|
+
knex,
|
|
42
|
+
});
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
> The `PostgresAnalyticsStore` may also be created with optional contructor arguments that may be helpful for debugging or metrics collection.
|
|
46
|
+
|
|
47
|
+
```typescript
|
|
48
|
+
const store = new PostgresAnalyticsStore({
|
|
49
|
+
queryLogger: querydefaultQueryLogger("memory"),
|
|
50
|
+
resultsLogger: defaultResultsLogger("memory"),
|
|
51
|
+
profiler: new PassthroughAnalyticsProfiler(),
|
|
52
|
+
});
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
For more details on these optional constructor parameters, see the [Utilities](#utilities) section.
|
|
56
|
+
|
|
57
|
+
## Raw Queries
|
|
58
|
+
|
|
59
|
+
Though there is no method on `IAnalyticsStore` for running arbitrary queries, the `PostgresAnalyticsStore` implementation provides a `raw(sql: string)` method. This is used only in development, testing, and [benchmarking](https://github.com/powerhouse-inc/analytics-engine/blob/main/benchmarks/src/wasm.ts) situations and is not intended for production use cases.
|
|
60
|
+
|
|
61
|
+
```typescript
|
|
62
|
+
const results = store.raw(`select distinct unit from "AnalyticsSeries"`);
|
|
63
|
+
```
|
package/docs/academy/02-AdvancedTutorial/04-WorkWithData/06-Analytics Engine/typescript/schema.md
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# Database Schema
|
|
2
|
+
|
|
3
|
+

|
|
4
|
+
*Database table structures for the analytics engine.*
|
|
5
|
+
|
|
6
|
+
Analytics information is stored in three database tables:
|
|
7
|
+
|
|
8
|
+
- `AnalyticsSeries`: This table stores the raw data over a period with its metric, value, unit, function and parameters.
|
|
9
|
+
- `AnalyticsDimension`: This table stores all available dimensions for each series.
|
|
10
|
+
- `AnalyticsSeries_AnalyticsDimension`: Many to many lookup table, storing relationships between series and dimensions.
|
|
11
|
+
|
|
12
|
+
The `browser` package automatically creates this schema in the PGLite instance.
|
|
13
|
+
|
|
14
|
+
The `postgres` package, on the other hand, [contains an init script](https://github.com/powerhouse-inc/analytics-engine/blob/main/pg/test/scripts/initdb.sh) that can be used to create the correct schema in your database.
|