diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 303edabc..44988176 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -128,3 +128,8 @@ jobs: uses: GabrielBB/xvfb-action@v1 with: run: npm run test:watermelondb + + - name: test:pglite + uses: GabrielBB/xvfb-action@v1 + with: + run: npm run test:pglite diff --git a/angular.json b/angular.json index 1bcfd546..3a98ddc6 100644 --- a/angular.json +++ b/angular.json @@ -688,6 +688,115 @@ } } } + }, + "pglite": { + "projectType": "application", + "schematics": { + "@schematics/angular:component": { + "style": "less" + }, + "@schematics/angular:application": { + "strict": true + } + }, + "root": "projects/pglite", + "sourceRoot": "projects/pglite/src", + "prefix": "app", + "architect": { + "build": { + "builder": "@angular-devkit/build-angular:browser", + "options": { + "outputPath": "dist/pglite", + "index": "projects/pglite/src/index.html", + "main": "projects/pglite/src/main.ts", + "polyfills": "projects/pglite/src/polyfills.ts", + "tsConfig": "projects/pglite/tsconfig.app.json", + "inlineStyleLanguage": "less", + "assets": [ + "projects/pglite/src/favicon.ico", + "projects/pglite/src/assets", + { + "glob": "*.wasm", + "input": "node_modules/@electric-sql/pglite/dist/", + "output": "./" + }, + { + "glob": "pglite.data", + "input": "node_modules/@electric-sql/pglite/dist/", + "output": "./" + } + ], + "styles": [ + "projects/pglite/src/styles.less" + ], + "scripts": [] + }, + "configurations": { + "production": { + "budgets": [ + { + "type": "initial", + "maximumWarning": "500kb", + "maximumError": "5mb" + }, + { + "type": "anyComponentStyle", + "maximumWarning": "150kb", + "maximumError": "150kb" + } + ], + "fileReplacements": [ + { + "replace": "projects/pglite/src/environments/environment.ts", + "with": "projects/pglite/src/environments/environment.prod.ts" + } + ], + "optimization": true, + "outputHashing": "all", + "namedChunks": false, + "extractLicenses": true, + "vendorChunk": false, + "buildOptimizer": true + }, + "development": { + "buildOptimizer": false, + "optimization": false, + "vendorChunk": true, + "extractLicenses": false, + "sourceMap": true, + "namedChunks": true + } + }, + "defaultConfiguration": "production" + }, + "serve": { + "builder": "@angular-devkit/build-angular:dev-server", + "configurations": { + "production": { + "buildTarget": "pglite:build:production" + }, + "development": { + "buildTarget": "pglite:build:development" + } + }, + "defaultConfiguration": "development" + }, + "extract-i18n": { + "builder": "@angular-devkit/build-angular:extract-i18n", + "options": { + "buildTarget": "pglite:build" + } + }, + "lint": { + "builder": "@angular-eslint/builder:lint", + "options": { + "lintFilePatterns": [ + "projects/pglite/**/*.ts", + "projects/pglite/**/*.html" + ] + } + } + } } }, "cli": { @@ -699,4 +808,4 @@ "enabled": false } } -} +} \ No newline at end of file diff --git a/measure-metrics.sh b/measure-metrics.sh index 5fe30e0b..df99a2bd 100644 --- a/measure-metrics.sh +++ b/measure-metrics.sh @@ -10,5 +10,6 @@ npm run test:pouchdb npm run test:rxdb-lokijs npm run test:rxdb-dexie npm run test:watermelondb +npm run test:pglite npm run aggregate-metrics diff --git a/package.json b/package.json index 2f50409a..0732ff46 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ "analyze:watermelondb": "webpack-bundle-analyzer ./dist/watermelondb/stats.json", "analyze:rxdb-lokijs": "webpack-bundle-analyzer ./dist/rxdb-lokijs/stats.json", "analyze:rxdb-dexie": "webpack-bundle-analyzer ./dist/rxdb-dexie/stats.json", + "analyze:pglite": "webpack-bundle-analyzer ./dist/pglite/stats.json", "build": "rimraf ./dist && npm-run-all build:*", "build:aws": "ng build --configuration production --aot --no-progress --project aws --stats-json", "build:firebase": "ng build --configuration production --aot --no-progress --project firebase --stats-json", @@ -19,6 +20,7 @@ "build:rxdb-lokijs": "ng build --configuration production --aot --no-progress --project rxdb-lokijs --stats-json", "build:rxdb-dexie": "ng build --configuration production --aot --no-progress --project rxdb-dexie --stats-json", "build:watermelondb": "ng build --configuration production --aot --no-progress --project watermelondb --stats-json", + "build:pglite": "ng build --configuration production --aot --no-progress --project pglite --stats-json", "build:template": "ng build --configuration production --aot --no-progress --stats-json", "lint": "ng lint", "lint:fix": "ng lint --fix", @@ -33,12 +35,14 @@ "dev:rxdb-lokijs": "concurrently \"npm run server:rxdb\" \"npm run client:rxdb-lokijs\"", "dev:rxdb-dexie": "concurrently \"npm run server:rxdb\" \"npm run client:rxdb-dexie\"", "dev:watermelondb": "concurrently \"npm run server:rxdb\" \"npm run client:watermelondb\"", + "dev:pglite": "npm run client:pglite", "start:aws": "http-server ./dist/aws -p 3000 -c 2592000", "start:firebase": "concurrently \"npm run server:firebase\" \"sleep 10 && http-server ./dist/firebase -p 3000 -c 2592000\"", "start:pouchdb": "concurrently \"npm run server:pouchdb\" \"http-server ./dist/pouchdb -p 3000 -c 2592000\" --kill-others --success first", "start:rxdb-lokijs": "concurrently \"npm run server:rxdb\" \"http-server ./dist/rxdb-lokijs -p 3000 -c 2592000\"", "start:rxdb-dexie": "concurrently \"npm run server:rxdb\" \"http-server ./dist/rxdb-dexie -p 3000 -c 2592000\"", "start:watermelondb": "http-server ./dist/watermelondb -p 3000 -c 2592000", + "start:pglite": "http-server ./dist/pglite -p 3000 -c 2592000", "server:firebase": "concurrently \"firebase emulators:start --only firestore\" \"npm run server:firebase:import\"", "server:firebase:setup": "firebase setup:emulators:firestore", "server:firebase:import": "ts-node --skip-project ./projects/firebase/src/import-example-data.ts", @@ -54,6 +58,7 @@ "client:rxdb-lokijs": "ng serve --project rxdb-lokijs --port 3000", "client:rxdb-dexie": "ng serve --project rxdb-dexie --port 3000", "client:watermelondb": "ng serve --project watermelondb --port 3000", + "client:pglite": "ng serve --project pglite --port 3000", "test:wait-for-frontend": "ts-node --skip-project ./scripts/wait-for-frontend.ts", "test": "testcafe chrome:headless --hostname localhost -e test/e2e.test.ts", "test:aws": "PROJECT_KEY=aws NODE_OPTIONS=--max_old_space_size=4096 concurrently \"npm run start:aws\" \"npm run test:wait-for-frontend && npm run test\" --kill-others --success first", @@ -61,7 +66,8 @@ "test:pouchdb": "PROJECT_KEY=pouchdb NODE_OPTIONS=--max_old_space_size=4096 concurrently \"npm run start:pouchdb\" \"npm run test:wait-for-frontend && npm run test\" --kill-others --success first", "test:rxdb-lokijs": "PROJECT_KEY=rxdb-lokijs NODE_OPTIONS=--max_old_space_size=4096 concurrently \"npm run start:rxdb-lokijs\" \"npm run test:wait-for-frontend && npm run test\" --kill-others --success first", "test:rxdb-dexie": "PROJECT_KEY=rxdb-dexie NODE_OPTIONS=--max_old_space_size=4096 concurrently \"npm run start:rxdb-dexie\" \"npm run test:wait-for-frontend && npm run test\" --kill-others --success first", - "test:watermelondb": "PROJECT_KEY=watermelondb NODE_OPTIONS=--max_old_space_size=4096 concurrently \"npm run start:watermelondb\" \"npm run test:wait-for-frontend && npm run test\" --kill-others --success first" + "test:watermelondb": "PROJECT_KEY=watermelondb NODE_OPTIONS=--max_old_space_size=4096 concurrently \"npm run start:watermelondb\" \"npm run test:wait-for-frontend && npm run test\" --kill-others --success first", + "test:pglite": "PROJECT_KEY=pglite NODE_OPTIONS=--max_old_space_size=4096 concurrently \"npm run start:pglite\" \"npm run test:wait-for-frontend && npm run test\" --kill-others --success first" }, "private": true, "dependencies": { @@ -79,6 +85,7 @@ "@aws-amplify/datastore": "3.12.12", "@aws-amplify/ui-angular": "5.3.2", "@babel/runtime": "7.29.2", + "@electric-sql/pglite": "^0.4.3", "@nozbe/watermelondb": "0.24.0", "@types/express": "4.17.25", "@types/express-serve-static-core": "4.19.8", @@ -127,15 +134,15 @@ "@angular/language-service": "17.3.12", "@types/faker": "5.5.9", "@types/jsonwebtoken": "9.0.10", + "@types/lokijs": "1.5.14", "@types/node": "20.19.39", + "@types/pouchdb": "6.4.2", + "@types/pouchdb-find": "7.3.3", + "@types/ws": "8.18.1", "@typescript-eslint/eslint-plugin": "7.18.0", "@typescript-eslint/eslint-plugin-tslint": "7.0.2", "@typescript-eslint/parser": "7.18.0", "async-test-util": "2.5.0", - "@types/lokijs": "1.5.14", - "@types/pouchdb": "6.4.2", - "@types/pouchdb-find": "7.3.3", - "@types/ws": "8.18.1", "concurrently": "8.2.2", "eslint": "8.57.1", "eslint-plugin-import": "2.32.0", diff --git a/projects/pglite/.eslintrc.json b/projects/pglite/.eslintrc.json new file mode 100644 index 00000000..1e8a7301 --- /dev/null +++ b/projects/pglite/.eslintrc.json @@ -0,0 +1,43 @@ +{ + "extends": "../../.eslintrc.json", + "ignorePatterns": [ + "!**/*" + ], + "overrides": [ + { + "files": [ + "*.ts" + ], + "parserOptions": { + "project": [ + "projects/pglite/tsconfig.app.json" + ], + "createDefaultProgram": true + }, + "rules": { + "@angular-eslint/directive-selector": [ + "error", + { + "type": "attribute", + "prefix": "app", + "style": "camelCase" + } + ], + "@angular-eslint/component-selector": [ + "error", + { + "type": "element", + "prefix": "app", + "style": "kebab-case" + } + ] + } + }, + { + "files": [ + "*.html" + ], + "rules": {} + } + ] +} diff --git a/projects/pglite/src/app/app.component.html b/projects/pglite/src/app/app.component.html new file mode 100644 index 00000000..2f29fc69 --- /dev/null +++ b/projects/pglite/src/app/app.component.html @@ -0,0 +1 @@ + diff --git a/projects/pglite/src/app/app.component.less b/projects/pglite/src/app/app.component.less new file mode 100644 index 00000000..e69de29b diff --git a/projects/pglite/src/app/app.component.ts b/projects/pglite/src/app/app.component.ts new file mode 100644 index 00000000..fda4d050 --- /dev/null +++ b/projects/pglite/src/app/app.component.ts @@ -0,0 +1,20 @@ +import { ChangeDetectionStrategy, Component } from '@angular/core'; +import { + LogicInterface +} from '../../../../src/app/logic-interface.interface'; + +import { + Logic +} from './app.logic'; + +@Component({ + selector: 'app-root', + templateUrl: './app.component.html', + styleUrls: ['./app.component.less'], + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class AppComponent { + title = 'pglite'; + + public logic: LogicInterface = new Logic(); +} diff --git a/projects/pglite/src/app/app.logic.ts b/projects/pglite/src/app/app.logic.ts new file mode 100644 index 00000000..fa778455 --- /dev/null +++ b/projects/pglite/src/app/app.logic.ts @@ -0,0 +1,224 @@ +import { + Observable, + combineLatest, + of +} from 'rxjs'; +import { + switchMap, + map, + shareReplay, + startWith, + mergeMap, + filter, + tap +} from 'rxjs/operators'; +import { + LogicInterface +} from '../../../../src/app/logic-interface.interface'; +import { + Message, + UserWithLastMessage, + User, + AddMessage, + UserPair, + Search +} from '../../../../src/shared/types'; +import { + DatabaseType, + createDatabase +} from './services/database.service'; +import { + sortByNewestFirst +} from 'src/shared/util-server'; +import { RXJS_SHARE_REPLAY_DEFAULTS } from 'rxdb'; + +export class Logic implements LogicInterface { + private dbPromise: Promise = createDatabase(); + private db!: DatabaseType; + + constructor() { + this.dbPromise.then(db => this.db = db); + } + + getUserByName(userName$: Observable): Observable { + return userName$.pipe( + mergeMap((userName) => this.dbPromise.then(() => userName)), + switchMap(userName => { + return this.db.users$.pipe( + startWith(undefined), + map(() => userName) + ); + }), + switchMap(async (userName) => { + let result = await this.db.db.query( + `SELECT id, "createdAt" FROM users WHERE id = $1 LIMIT 1`, + [userName] + ); + if (result.rows.length === 0) { + await this.db.db.query( + `INSERT INTO users (id, "createdAt") VALUES ($1, $2)`, + [userName, new Date().getTime()] + ); + result = await this.db.db.query( + `SELECT id, "createdAt" FROM users WHERE id = $1 LIMIT 1`, + [userName] + ); + } + return result.rows[0] ?? null; + }), + filter((doc): doc is User => !!doc) + ); + } + + getSearchResults(search$: Observable): Observable { + return search$.pipe( + switchMap(search => { + return this.db.messages$.pipe( + startWith(undefined), + map(() => search) + ); + }), + switchMap(async (search) => { + const result = await this.db.db.query( + `SELECT id, text, "createdAt", read, sender, reciever + FROM messages + WHERE text ILIKE $1 + AND (sender = $2 OR reciever = $2)`, + [`%${search.searchTerm}%`, search.ownUser.id] + ); + return { search, messages: result.rows }; + }), + switchMap(async ({ search, messages }) => { + return Promise.all( + messages.map(async (message) => { + const otherUserId = message.sender === search.ownUser.id + ? message.reciever + : message.sender; + const userResult = await this.db.db.query( + `SELECT id, "createdAt" FROM users WHERE id = $1 LIMIT 1`, + [otherUserId] + ); + return { + user: userResult.rows[0], + message + } as UserWithLastMessage; + }) + ); + }) + ); + } + + getUsersWithLastMessages(ownUser$: Observable): Observable { + const usersNotOwn$ = ownUser$.pipe( + switchMap(ownUser => { + return this.db.users$.pipe( + startWith(undefined), + map(() => ownUser) + ); + }), + switchMap(async (ownUser) => { + const result = await this.db.db.query( + `SELECT id, "createdAt" FROM users WHERE id != $1`, + [ownUser.id] + ); + return result.rows; + }), + shareReplay(RXJS_SHARE_REPLAY_DEFAULTS) + ); + + const usersWithLastMessage$: Observable = combineLatest([ + ownUser$, + usersNotOwn$ + ]).pipe( + map(([ownUser, usersNotOwn]) => { + return usersNotOwn.map((user) => { + return this.getLastMessageOfUserPair({ + user1: ownUser, + user2: user + }).pipe( + map(message => ({ user, message })), + shareReplay(RXJS_SHARE_REPLAY_DEFAULTS) + ); + }); + }), + switchMap(streams => streams.length === 0 ? of([]) : combineLatest(streams)), + map(usersWithLastMessage => sortByNewestFirst(usersWithLastMessage as any)) + ); + + return usersWithLastMessage$; + } + + private getLastMessageOfUserPair( + userPair: UserPair + ): Observable { + return this.db.messages$.pipe( + startWith(undefined), + switchMap(async () => { + const result = await this.db.db.query( + `SELECT id, text, "createdAt", read, sender, reciever + FROM messages + WHERE (sender = $1 AND reciever = $2) + OR (sender = $2 AND reciever = $1) + ORDER BY "createdAt" DESC + LIMIT 1`, + [userPair.user1.id, userPair.user2.id] + ); + return result.rows[0]; + }), + shareReplay(RXJS_SHARE_REPLAY_DEFAULTS) + ); + } + + public getMessagesForUserPair( + userPair$: Observable + ): Observable { + return userPair$.pipe( + switchMap(userPair => { + return this.db.messages$.pipe( + startWith(undefined), + map(() => userPair) + ); + }), + switchMap(async (userPair) => { + const result = await this.db.db.query( + `SELECT id, text, "createdAt", read, sender, reciever + FROM messages + WHERE (sender = $1 AND reciever = $2) + OR (sender = $2 AND reciever = $1) + ORDER BY "createdAt" ASC`, + [userPair.user1.id, userPair.user2.id] + ); + return result.rows; + }) + ); + } + + async addMessage(message: AddMessage): Promise { + await this.dbPromise; + const m = message.message; + await this.db.db.query( + `INSERT INTO messages (id, text, "createdAt", read, sender, reciever) + VALUES ($1, $2, $3, $4, $5, $6) + ON CONFLICT (id) DO NOTHING`, + [m.id, m.text, m.createdAt, m.read, m.sender, m.reciever] + ); + } + + async addUser(user: User): Promise { + await this.dbPromise; + await this.db.db.query( + `INSERT INTO users (id, "createdAt") + VALUES ($1, $2) + ON CONFLICT (id) DO NOTHING`, + [user.id, user.createdAt] + ); + } + + async hasData(): Promise { + await this.dbPromise; + const result = await this.db.db.query<{ count: string }>( + `SELECT COUNT(*) as count FROM users` + ); + return parseInt(result.rows[0]?.count ?? '0', 10) > 0; + } +} diff --git a/projects/pglite/src/app/app.module.ts b/projects/pglite/src/app/app.module.ts new file mode 100644 index 00000000..6dda94b2 --- /dev/null +++ b/projects/pglite/src/app/app.module.ts @@ -0,0 +1,32 @@ +import { BrowserModule } from '@angular/platform-browser'; +import { NgModule } from '@angular/core'; + +import { AppComponent } from './app.component'; +import { + ChatModule +} from '../../../../src/app/chat.module'; + +import { + APP_BASE_HREF, + LocationStrategy, + PathLocationStrategy +} from '@angular/common'; + +@NgModule({ + declarations: [ + AppComponent + ], + imports: [ + BrowserModule, + ChatModule + ], + providers: [ + { provide: APP_BASE_HREF, useValue: '/' }, + { + provide: LocationStrategy, + useClass: PathLocationStrategy + } + ], + bootstrap: [AppComponent] +}) +export class AppModule { } diff --git a/projects/pglite/src/app/services/database.service.ts b/projects/pglite/src/app/services/database.service.ts new file mode 100644 index 00000000..4e14f2d5 --- /dev/null +++ b/projects/pglite/src/app/services/database.service.ts @@ -0,0 +1,108 @@ +import { PGlite } from '@electric-sql/pglite'; +import { Subject } from 'rxjs'; +import { shareReplay } from 'rxjs/operators'; +import { Observable } from 'rxjs'; +import { RXJS_SHARE_REPLAY_DEFAULTS } from 'rxdb'; +import { logTime } from 'src/shared/util-browser'; + +export interface DatabaseType { + db: PGlite; + users$: Observable; + messages$: Observable; +} + +/** + * Creates the PGlite database with IndexedDB persistence, + * initializes tables and change-notification triggers. + */ +export async function createDatabase(): Promise { + logTime('createDatabase()'); + + /** + * Pre-fetch WASM modules and the FS bundle via proper HTTP URLs so that + * WebAssembly.compileStreaming receives responses with the correct + * "application/wasm" MIME type. Without this, webpack replaces + * import.meta.url with hardcoded file:// paths which Chrome serves with + * "application/octet-stream", causing a streaming-compile error that is + * caught internally but still logged to console.error (failing the tests). + */ + const [pgliteWasmModule, initdbWasmModule, fsBundleBlob] = await Promise.all([ + WebAssembly.compileStreaming(fetch('/pglite.wasm')), + WebAssembly.compileStreaming(fetch('/initdb.wasm')), + fetch('/pglite.data').then(r => r.blob()) + ]); + + const db = new PGlite('idb://chat-db', { + pgliteWasmModule, + initdbWasmModule, + fsBundle: fsBundleBlob + }); + await db.waitReady; + + logTime('create tables'); + await db.exec(` + CREATE TABLE IF NOT EXISTS users ( + id TEXT PRIMARY KEY, + "createdAt" BIGINT NOT NULL + ); + + CREATE TABLE IF NOT EXISTS messages ( + id TEXT PRIMARY KEY, + text TEXT NOT NULL, + "createdAt" BIGINT NOT NULL, + read BOOLEAN NOT NULL, + sender TEXT NOT NULL, + reciever TEXT NOT NULL + ); + + CREATE INDEX IF NOT EXISTS messages_created_at_idx ON messages ("createdAt"); + + CREATE OR REPLACE FUNCTION notify_users_change() + RETURNS trigger AS $$ + BEGIN + PERFORM pg_notify('users_change', ''); + RETURN NEW; + END; + $$ LANGUAGE plpgsql; + + CREATE OR REPLACE FUNCTION notify_messages_change() + RETURNS trigger AS $$ + BEGIN + PERFORM pg_notify('messages_change', ''); + RETURN NEW; + END; + $$ LANGUAGE plpgsql; + + DROP TRIGGER IF EXISTS users_change_trigger ON users; + CREATE TRIGGER users_change_trigger + AFTER INSERT OR UPDATE OR DELETE ON users + FOR EACH ROW EXECUTE FUNCTION notify_users_change(); + + DROP TRIGGER IF EXISTS messages_change_trigger ON messages; + CREATE TRIGGER messages_change_trigger + AFTER INSERT OR UPDATE OR DELETE ON messages + FOR EACH ROW EXECUTE FUNCTION notify_messages_change(); + `); + logTime('create tables DONE'); + + const usersSubject = new Subject(); + const messagesSubject = new Subject(); + + await db.listen('users_change', () => { + usersSubject.next(); + }); + await db.listen('messages_change', () => { + messagesSubject.next(); + }); + + logTime('createDatabase() DONE'); + return { + db, + users$: usersSubject.asObservable().pipe( + shareReplay(RXJS_SHARE_REPLAY_DEFAULTS) + ), + messages$: messagesSubject.asObservable().pipe( + shareReplay(RXJS_SHARE_REPLAY_DEFAULTS) + ) + }; +} diff --git a/projects/pglite/src/assets/email-pattern.png b/projects/pglite/src/assets/email-pattern.png new file mode 100644 index 00000000..8bedad4e Binary files /dev/null and b/projects/pglite/src/assets/email-pattern.png differ diff --git a/projects/pglite/src/environments/environment.prod.ts b/projects/pglite/src/environments/environment.prod.ts new file mode 100644 index 00000000..3612073b --- /dev/null +++ b/projects/pglite/src/environments/environment.prod.ts @@ -0,0 +1,3 @@ +export const environment = { + production: true +}; diff --git a/projects/pglite/src/environments/environment.ts b/projects/pglite/src/environments/environment.ts new file mode 100644 index 00000000..5dd10dc0 --- /dev/null +++ b/projects/pglite/src/environments/environment.ts @@ -0,0 +1,7 @@ +// This file can be replaced during build by using the `fileReplacements` array. +// `ng build --prod` replaces `environment.ts` with `environment.prod.ts`. +// The list of file replacements can be found in `angular.json`. + +export const environment = { + production: false +}; diff --git a/projects/pglite/src/favicon.ico b/projects/pglite/src/favicon.ico new file mode 100644 index 00000000..997406ad Binary files /dev/null and b/projects/pglite/src/favicon.ico differ diff --git a/projects/pglite/src/index.html b/projects/pglite/src/index.html new file mode 100644 index 00000000..fe861b55 --- /dev/null +++ b/projects/pglite/src/index.html @@ -0,0 +1,26 @@ + + + + + + + PGlite + + + + + + + + + + diff --git a/projects/pglite/src/main.ts b/projects/pglite/src/main.ts new file mode 100644 index 00000000..c7b673cf --- /dev/null +++ b/projects/pglite/src/main.ts @@ -0,0 +1,12 @@ +import { enableProdMode } from '@angular/core'; +import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; + +import { AppModule } from './app/app.module'; +import { environment } from './environments/environment'; + +if (environment.production) { + enableProdMode(); +} + +platformBrowserDynamic().bootstrapModule(AppModule) + .catch(err => console.error(err)); diff --git a/projects/pglite/src/polyfills.ts b/projects/pglite/src/polyfills.ts new file mode 100644 index 00000000..3c1fabad --- /dev/null +++ b/projects/pglite/src/polyfills.ts @@ -0,0 +1,34 @@ +import { logPageLoadTime } from '../../../src/shared/util-browser'; +logPageLoadTime(); + +(window as any).global = window; + +/** + * This file includes polyfills needed by Angular and is loaded before the app. + * You can add your own extra polyfills to this file. + * + * This file is divided into 2 sections: + * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers. + * 2. Application imports. Files imported after ZoneJS that should be loaded before your main + * file. + * + * The current setup is for so-called "evergreen" browsers; the last versions of browsers that + * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera), + * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile. + * + * Learn more in https://angular.io/guide/browser-support + */ + +/*************************************************************************************************** + * BROWSER POLYFILLS + */ + +/*************************************************************************************************** + * Zone JS is required by default for Angular itself. + */ +import 'zone.js'; // Included with Angular CLI. + + +/*************************************************************************************************** + * APPLICATION IMPORTS + */ diff --git a/projects/pglite/src/styles.less b/projects/pglite/src/styles.less new file mode 100644 index 00000000..2288e329 --- /dev/null +++ b/projects/pglite/src/styles.less @@ -0,0 +1,2 @@ +@import '@angular/material/prebuilt-themes/indigo-pink.css'; +@import "../../../src/styles.css"; diff --git a/projects/pglite/tsconfig.app.json b/projects/pglite/tsconfig.app.json new file mode 100644 index 00000000..c80e4eea --- /dev/null +++ b/projects/pglite/tsconfig.app.json @@ -0,0 +1,17 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "../../out-tsc/app", + "types": [] + }, + "files": [ + "src/main.ts", + "src/polyfills.ts" + ], + "include": [ + "src/**/*.ts", + "src/**/*.d.ts" + ], + "exclude": [ + ] +}