11 | [ ] Regression (a behavior that used to work and stopped working in a new release)
12 | [ ] Bug report
13 | [ ] Performance issue
14 | [ ] Feature request
15 | [ ] Documentation issue or request
16 | [ ] Support request => https://github.com/voznik/ngx-odm/blob/master/CONTRIBUTING.md
17 | [ ] Other... Please describe:
18 |
19 |
20 | ## Current behavior
21 |
22 |
23 |
24 | ## Expected behavior
25 |
26 |
27 |
28 | ## Minimal reproduction of the problem with instructions
29 |
30 |
39 |
40 | **Note:** Our policy is that issues reported without a reproduction will be closed immediately and then reopened once a reproduction has been provided. Please respect the developers of this project by doing this. We give of our personal time to work on this project and would rather be spending our time fixing or enhancing the library than chasing down badly described or unreproducable issues.
41 | Please delete this note once you have read it.
42 |
43 | ## What is the motivation / use case for changing the behavior?
44 |
45 |
46 |
47 | ## Environment
48 |
49 |
50 | Libs:
51 | - @angular/core version: X.Y.Z
52 | - @ngx-odm/rxdb version: X.Y.Z
53 |
54 |
55 | Browser:
56 | - [ ] Chrome (desktop) version XX
57 | - [ ] Chrome (Android) version XX
58 | - [ ] Chrome (iOS) version XX
59 | - [ ] Firefox version XX
60 | - [ ] Safari (desktop) version XX
61 | - [ ] Safari (iOS) version XX
62 | - [ ] IE version XX
63 | - [ ] Edge version XX
64 |
65 | For Tooling issues:
66 | - Node version: XX
67 | - Platform:
68 |
69 | Others:
70 |
71 |
72 |
--------------------------------------------------------------------------------
/packages/streamlit-rxdb-dataframe/rxdb_dataframe/test___init__.py:
--------------------------------------------------------------------------------
1 | import pandas as pd
2 | import pytest
3 |
4 | from rxdb_dataframe import get_dataframe_by_schema
5 |
6 |
7 | def test_get_dataframe_by_schema():
8 | # Test case 1: Empty schema
9 | schema = {}
10 | expected_df = pd.DataFrame()
11 | assert get_dataframe_by_schema(schema).equals(expected_df)
12 |
13 | # Test case 2: Schema with string properties
14 | schema = {
15 | "properties": {
16 | "name": {"type": "string"},
17 | "age": {"type": "string"},
18 | "city": {"type": "string"},
19 | }
20 | }
21 | expected_df = pd.DataFrame(
22 | {
23 | "name": pd.Series(dtype="object"),
24 | "age": pd.Series(dtype="object"),
25 | "city": pd.Series(dtype="object"),
26 | }
27 | )
28 | assert get_dataframe_by_schema(schema).equals(expected_df)
29 |
30 | # Test case 3: Schema with boolean properties
31 | schema = {
32 | "properties": {"is_active": {"type": "boolean"}, "has_permission": {"type": "boolean"}}
33 | }
34 | expected_df = pd.DataFrame(
35 | {"is_active": pd.Series(dtype="bool"), "has_permission": pd.Series(dtype="bool")}
36 | )
37 | assert get_dataframe_by_schema(schema).equals(expected_df)
38 |
39 |
40 | def test_get_dataframe_by_schema():
41 | # Test case 1: Empty schema
42 | schema = {}
43 | expected_df = pd.DataFrame()
44 | assert get_dataframe_by_schema(schema).equals(expected_df)
45 |
46 | # Test case 2: Schema with string properties
47 | schema = {
48 | "properties": {
49 | "name": {"type": "string"},
50 | "age": {"type": "string"},
51 | "city": {"type": "string"},
52 | }
53 | }
54 | expected_df = pd.DataFrame(
55 | {
56 | "name": pd.Series(dtype="object"),
57 | "age": pd.Series(dtype="object"),
58 | "city": pd.Series(dtype="object"),
59 | }
60 | )
61 | assert get_dataframe_by_schema(schema).equals(expected_df)
62 |
63 | # Test case 3: Schema with boolean properties
64 | schema = {
65 | "properties": {"is_active": {"type": "boolean"}, "has_permission": {"type": "boolean"}}
66 | }
67 | expected_df = pd.DataFrame(
68 | {"is_active": pd.Series(dtype="bool"), "has_permission": pd.Series(dtype="bool")}
69 | )
70 | assert get_dataframe_by_schema(schema).equals(expected_df)
71 |
72 |
73 | if __name__ == "__main__":
74 | pytest.main([__file__])
75 |
--------------------------------------------------------------------------------
/tsconfig.base.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://json.schemastore.org/tsconfig",
3 | "compileOnSave": false,
4 | "compilerOptions": {
5 | "baseUrl": ".",
6 | "outDir": "./dist/out-tsc",
7 | "rootDir": ".",
8 | "sourceMap": true,
9 | "declaration": false,
10 | "allowSyntheticDefaultImports": true,
11 | "useUnknownInCatchVariables": false,
12 | "emitDecoratorMetadata": true,
13 | "esModuleInterop": true,
14 | "experimentalDecorators": true,
15 | "importHelpers": true,
16 | "lib": ["es2022", "dom"],
17 | "module": "ESNext",
18 | "moduleResolution": "Node",
19 | "resolveJsonModule": true,
20 | "skipDefaultLibCheck": true,
21 | "skipLibCheck": true,
22 | "ignoreDeprecations": "5.0",
23 | "suppressImplicitAnyIndexErrors": true,
24 | "target": "es2022",
25 | "typeRoots": ["node_modules/@types"],
26 | "types": ["node"],
27 | "paths": {
28 | "@ngx-odm/react": ["packages/react/src/index.ts"],
29 | "@ngx-odm/rxdb": ["dist/packages/rxdb", "packages/rxdb/src/index.ts"],
30 | "@ngx-odm/rxdb/collection": [
31 | "dist/packages/rxdb/collection",
32 | "packages/rxdb/collection/src/index.ts"
33 | ],
34 | "@ngx-odm/rxdb/config": [
35 | "dist/packages/rxdb/config",
36 | "packages/rxdb/config/src/index.ts"
37 | ],
38 | "@ngx-odm/rxdb/core": ["dist/packages/rxdb/core", "packages/rxdb/core/src/index.ts"],
39 | "@ngx-odm/rxdb/prepare": [
40 | "dist/packages/rxdb/prepare",
41 | "packages/rxdb/prepare/src/index.ts"
42 | ],
43 | "@ngx-odm/rxdb/query-params": [
44 | "dist/packages/rxdb/query-params",
45 | "packages/rxdb/query-params/src/index.ts"
46 | ],
47 | "@ngx-odm/rxdb/replication-kinto": [
48 | "dist/packages/rxdb/replication-kinto",
49 | "packages/rxdb/replication-kinto/src/index.ts"
50 | ],
51 | "@ngx-odm/rxdb/signals": [
52 | "dist/packages/rxdb/signals",
53 | "packages/rxdb/signals/src/index.ts"
54 | ],
55 | "@ngx-odm/rxdb/sreamlit": ["packages/streamlit-rxdb-dataframe-frontend/src/index.ts"],
56 | "@ngx-odm/rxdb/testing": [
57 | "dist/packages/rxdb/testing",
58 | "packages/rxdb/testing/src/index.ts"
59 | ],
60 | "@ngx-odm/rxdb/utils": [
61 | "dist/packages/rxdb/utils",
62 | "packages/rxdb/utils/src/index.ts"
63 | ],
64 | "@package": ["packages/rxdb/package.json"],
65 | "@shared": ["examples/shared/index.ts"]
66 | }
67 | },
68 | "exclude": ["node_modules", "tmp"]
69 | }
70 |
--------------------------------------------------------------------------------
/packages/rxdb/query-params/src/utils.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable @typescript-eslint/ban-types, @typescript-eslint/no-explicit-any */
2 | import { NgxRxdbUtils } from '@ngx-odm/rxdb/utils';
3 | import { includeKeys } from 'filter-obj';
4 | import queryString from 'query-string';
5 | import type { MangoQuery, MangoQuerySelector, MangoQuerySortPart, RxSchema } from 'rxdb';
6 |
7 | const { keys, isEmpty, isNullOrUndefined } = NgxRxdbUtils;
8 |
9 | /**
10 | * Ensure that all top level fields are included in the schema.
11 | * Ensure that sort only runs on known fields
12 | * @param schema
13 | */
14 | export const normalizeMangoQuery = (
15 | { selector, sort, limit, skip }: MangoQuery,
16 | schema: RxSchema
17 | ): MangoQuery => {
18 | const schemaTopLevelFields = keys(schema.jsonSchema.properties);
19 | if (!isEmpty(selector)) {
20 | selector = includeKeys(selector as Record, key => {
21 | return (
22 | !key.startsWith('$') && // do not check operators
23 | !key.includes('.') && // skip this check on non-top-level
24 | schemaTopLevelFields.includes(key) // included in the schema
25 | );
26 | });
27 | }
28 | if (!isEmpty(sort)) {
29 | sort = sort?.filter(sortPart => schemaTopLevelFields.includes(keys(sortPart)[0]));
30 | }
31 |
32 | if (isNullOrUndefined(limit) || isNaN(limit as number)) {
33 | limit = undefined;
34 | }
35 | if (isNullOrUndefined(skip) || isNaN(skip as number)) {
36 | skip = undefined;
37 | }
38 |
39 | return { selector, sort, limit, skip };
40 | };
41 |
42 | export const parseUrlToMangoQuery = (url: string, schema: RxSchema): MangoQuery => {
43 | url = queryString.extract(url);
44 | const urlPart = queryString.pick(url, ['selector', 'sort', 'limit', 'skip']);
45 | const parsed: any = queryString.parse(urlPart, {
46 | parseNumbers: true,
47 | parseBooleans: true,
48 | });
49 | const { selector: _selector, sort: _sort, limit, skip } = parsed;
50 | const selector = _selector ? JSON.parse(_selector as string) : undefined;
51 | const sort = _sort ? JSON.parse(_sort as string) : undefined;
52 | /** Ensure that all top level fields are included in the schema. */
53 | const queryObj: MangoQuery = normalizeMangoQuery(
54 | {
55 | selector,
56 | sort,
57 | limit,
58 | skip,
59 | },
60 | schema
61 | );
62 | return queryObj;
63 | };
64 |
65 | export const stringifyParam = (
66 | param: MangoQuerySelector | MangoQuerySortPart[] | undefined
67 | ): string => {
68 | if (isNullOrUndefined(param)) return '';
69 | return JSON.stringify(param);
70 | };
71 |
--------------------------------------------------------------------------------
/packages/rxdb/query-params/src/utils.spec.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable @typescript-eslint/ban-ts-comment */
2 | import { TEST_SCHEMA, TestDocType } from '@ngx-odm/rxdb/testing';
3 | import { RxSchema, MangoQuery } from 'rxdb';
4 | import { normalizeMangoQuery, parseUrlToMangoQuery } from './utils';
5 |
6 | describe('query params utils', () => {
7 | const schema = new RxSchema(TEST_SCHEMA, () => Promise.resolve(''));
8 |
9 | describe('normalizeQuery', () => {
10 | it('should remove fields from selector that are not in the schema', () => {
11 | const query: MangoQuery = {
12 | selector: {
13 | completed: {
14 | $eq: true,
15 | },
16 | // @ts-expect-error
17 | unknownField: 'value',
18 | },
19 | sort: [],
20 | limit: 10,
21 | skip: 0,
22 | };
23 |
24 | const result = normalizeMangoQuery(query, schema);
25 |
26 | expect(result.selector).toEqual({
27 | completed: {
28 | $eq: true,
29 | },
30 | });
31 | });
32 |
33 | it('should remove sort parts that are not in the schema', () => {
34 | const query: MangoQuery = {
35 | selector: {},
36 | sort: [{ createdAt: 'asc' }, { unknownField: 'desc' }],
37 | limit: 10,
38 | skip: 0,
39 | };
40 |
41 | const result = normalizeMangoQuery(query, schema);
42 |
43 | expect(result.sort).toEqual([{ createdAt: 'asc' }]);
44 | });
45 |
46 | it('should set limit and skip to undefined if they are not valid numbers', () => {
47 | const query: MangoQuery = {
48 | selector: {},
49 | sort: [],
50 | limit: 'invalid',
51 | skip: 'invalid',
52 | } as any;
53 |
54 | const result = normalizeMangoQuery(query, schema);
55 |
56 | expect(result.limit).toBeUndefined();
57 | expect(result.skip).toBeUndefined();
58 | });
59 |
60 | // Add more test cases as needed
61 | });
62 |
63 | describe('parseUrlToMangoQuery', () => {
64 | it('should parse the URL and return the corresponding MangoQuery', () => {
65 | const url =
66 | 'http://localhost:4200/todos?filter=ACTIVE&limit=1&selector=%7B%22completed%22:%7B%22$eq%22:true%7D%7D&sort=%5B%7B%22createdAt%22:%22desc%22%7D%5D';
67 |
68 | const result = parseUrlToMangoQuery(url, schema);
69 |
70 | expect(result).toEqual({
71 | selector: {
72 | completed: {
73 | $eq: true,
74 | },
75 | },
76 | sort: [{ createdAt: 'desc' }],
77 | limit: 1,
78 | skip: undefined,
79 | });
80 | });
81 |
82 | // Add more test cases as needed
83 | });
84 | });
85 |
--------------------------------------------------------------------------------
/examples/standalone/project.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "standalone",
3 | "$schema": "../../node_modules/nx/schemas/project-schema.json",
4 | "projectType": "application",
5 | "prefix": "app",
6 | "sourceRoot": "examples/standalone/src",
7 | "tags": [],
8 | "targets": {
9 | "build": {
10 | "executor": "@angular-devkit/build-angular:application",
11 | "outputs": ["{options.outputPath}"],
12 | "options": {
13 | "outputPath": "dist/standalone",
14 | "index": "examples/standalone/src/index.html",
15 | "browser": "examples/standalone/src/main.ts",
16 | "polyfills": [],
17 | "tsConfig": "examples/standalone/tsconfig.app.json",
18 | "assets": ["examples/standalone/src/favicon.ico", "examples/standalone/src/assets"],
19 | "styles": [
20 | "node_modules/todomvc-common/base.css",
21 | "node_modules/todomvc-app-css/index.css"
22 | ],
23 | "scripts": []
24 | },
25 | "configurations": {
26 | "production": {
27 | "budgets": [
28 | {
29 | "type": "initial",
30 | "maximumWarning": "500kb",
31 | "maximumError": "1mb"
32 | },
33 | {
34 | "type": "anyComponentStyle",
35 | "maximumWarning": "2kb",
36 | "maximumError": "4kb"
37 | }
38 | ],
39 | "outputHashing": "all",
40 | "extractLicenses": false
41 | },
42 | "development": {
43 | "outputHashing": "none",
44 | "optimization": false,
45 | "extractLicenses": false,
46 | "sourceMap": true,
47 | "namedChunks": true
48 | }
49 | },
50 | "defaultConfiguration": "production"
51 | },
52 | "serve": {
53 | "executor": "@angular-devkit/build-angular:dev-server",
54 | "configurations": {
55 | "production": {
56 | "buildTarget": "standalone:build:production"
57 | },
58 | "development": {
59 | "buildTarget": "standalone:build:development"
60 | }
61 | },
62 | "defaultConfiguration": "development"
63 | },
64 | "extract-i18n": {
65 | "executor": "@angular-devkit/build-angular:extract-i18n",
66 | "options": {
67 | "buildTarget": "standalone:build"
68 | }
69 | },
70 | "lint": {
71 | "executor": "@nx/eslint:lint",
72 | "outputs": ["{options.outputFile}"]
73 | },
74 | "test": {
75 | "executor": "@nx/jest:jest",
76 | "outputs": ["{workspaceRoot}/coverage/{projectRoot}"],
77 | "options": {
78 | "jestConfig": "examples/standalone/jest.config.ts"
79 | }
80 | }
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/packages/rxdb/project.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "rxdb",
3 | "$schema": "../node_modules/nx/schemas/project-schema.json",
4 | "sourceRoot": "packages/rxdb/src",
5 | "tags": [],
6 | "projectType": "library",
7 | "targets": {
8 | "build": {
9 | "executor": "@nx/angular:package",
10 | "outputs": ["{workspaceRoot}/dist/{projectRoot}"],
11 | "options": {
12 | "project": "packages/rxdb/ng-package.json"
13 | },
14 | "configurations": {
15 | "production": {
16 | "tsConfig": "packages/rxdb/tsconfig.lib.prod.json"
17 | },
18 | "development": {
19 | "tsConfig": "packages/rxdb/tsconfig.lib.json"
20 | }
21 | },
22 | "defaultConfiguration": "production"
23 | },
24 | "test": {
25 | "executor": "@nx/jest:jest",
26 | "outputs": ["{workspaceRoot}/coverage/{projectRoot}"],
27 | "options": {
28 | "jestConfig": "packages/rxdb/jest.config.ts"
29 | }
30 | },
31 | "lint": {
32 | "executor": "@nx/eslint:lint",
33 | "outputs": ["{options.outputFile}"]
34 | },
35 | "version": {
36 | "executor": "@jscutlery/semver:version",
37 | "options": {
38 | "preset": "angular",
39 | "tagPrefix": "v",
40 | "noVerify": true,
41 | "baseBranch": "master",
42 | "push": true,
43 | "syncVersions": false,
44 | "commitMessageFormat": "release(ngx-odm/rxdb): new version ${version}",
45 | "skipCommitTypes": ["docs", "ci"],
46 | "postTargets": ["rxdb:github"],
47 | "types": [
48 | {
49 | "type": "feat",
50 | "section": "Features"
51 | },
52 | {
53 | "type": "fix",
54 | "section": "Bug Fixes"
55 | },
56 | {
57 | "type": "chore",
58 | "hidden": false
59 | },
60 | {
61 | "type": "docs",
62 | "hidden": false
63 | },
64 | {
65 | "type": "style",
66 | "hidden": false
67 | },
68 | {
69 | "type": "refactor",
70 | "section": "Features",
71 | "hidden": false
72 | },
73 | {
74 | "type": "perf",
75 | "section": "Features",
76 | "hidden": false
77 | },
78 | {
79 | "type": "test",
80 | "hidden": true
81 | },
82 | {
83 | "type": "build",
84 | "hidden": true
85 | }
86 | ]
87 | }
88 | },
89 | "github": {
90 | "executor": "@jscutlery/semver:github",
91 | "options": {
92 | "tag": "{tag}",
93 | "notes": "{notes}"
94 | }
95 | }
96 | }
97 | }
98 |
--------------------------------------------------------------------------------
/packages/streamlit-rxdb-dataframe/tests/test_rxdb_dataframe.py:
--------------------------------------------------------------------------------
1 | import pandas as pd
2 | from rxdb_dataframe import get_column_config, get_dataframe_by_schema
3 |
4 |
5 | def test_get_column_config():
6 | schema = {
7 | "properties": {
8 | "name": {"type": "string", "maxLength": 50},
9 | "age": {"type": "integer", "min": 0, "max": 100},
10 | "is_active": {"type": "boolean"},
11 | "gender": {"type": "string", "enum": ["Male", "Female", "Other"]},
12 | "birth_date": {"type": "string", "format": "date-time"},
13 | },
14 | "required": ["name", "age"],
15 | }
16 |
17 | column_config = get_column_config(schema)
18 |
19 | assert "name" in column_config
20 | assert column_config["name"]["type_config"]["type"] == "text"
21 | assert column_config["name"]["type_config"]["max_chars"] == 50
22 |
23 | assert "age" in column_config
24 | assert column_config["age"]["type_config"]["type"] == "number"
25 | assert column_config["age"]["type_config"]["min_value"] == 0
26 | assert column_config["age"]["type_config"]["max_value"] == 100
27 |
28 | assert "is_active" in column_config
29 | assert column_config["is_active"]["type_config"]["type"] == "checkbox"
30 |
31 | assert "gender" in column_config
32 | assert column_config["gender"]["type_config"]["type"] == "selectbox"
33 | assert column_config["gender"]["type_config"]["options"] == ["Male", "Female", "Other"]
34 |
35 | assert "birth_date" in column_config
36 | assert column_config["birth_date"]["type_config"]["type"] == "datetime"
37 | assert column_config["birth_date"]["type_config"]["format"] == "YYYY-MM-DD HH:mm"
38 |
39 |
40 | def test_get_dataframe_by_schema():
41 | # Test case 1: Empty schema
42 | schema = {}
43 | expected_df = pd.DataFrame()
44 | assert get_dataframe_by_schema(schema).equals(expected_df)
45 |
46 | # Test case 2: Schema with string properties
47 | schema = {
48 | "properties": {
49 | "name": {"type": "string"},
50 | "age": {"type": "string"},
51 | "city": {"type": "string"},
52 | }
53 | }
54 | expected_df = pd.DataFrame(
55 | {
56 | "name": pd.Series(dtype="object"),
57 | "age": pd.Series(dtype="object"),
58 | "city": pd.Series(dtype="object"),
59 | }
60 | )
61 | assert get_dataframe_by_schema(schema).equals(expected_df)
62 |
63 | # Test case 3: Schema with boolean properties
64 | schema = {
65 | "properties": {"is_active": {"type": "boolean"}, "has_permission": {"type": "boolean"}}
66 | }
67 | expected_df = pd.DataFrame(
68 | {"is_active": pd.Series(dtype="bool"), "has_permission": pd.Series(dtype="bool")}
69 | )
70 | assert get_dataframe_by_schema(schema).equals(expected_df)
71 |
--------------------------------------------------------------------------------
/packages/streamlit-rxdb-dataframe/rxdb_dataframe/frontend/src/lib/useEditingState.ts:
--------------------------------------------------------------------------------
1 | import { RxDBCollectionService } from '@ngx-odm/rxdb/collection';
2 | import { NgxRxdbUtils, type Entity } from '@ngx-odm/rxdb/utils';
3 | import equal from 'fast-deep-equal';
4 | import { useEffect, useRef, useState } from 'react';
5 | // eslint-disable-next-line @typescript-eslint/ban-ts-comment
6 | // @ts-expect-error
7 | import { v4 as uuid } from 'uuid';
8 | import { DataframeEditingState } from './RxDBDataframeArgs';
9 |
10 | const { logger, isEmpty } = NgxRxdbUtils;
11 |
12 | /**
13 | * Custom hook that manages the editing state of a Dataframe.
14 | * @param editingState - The editing state of the Dataframe.
15 | * @param collectionService - The service used to interact with the RxDB collection.
16 | * @returns A tuple containing the entities and a function to set the entities.
17 | */
18 | export const useEditedState = (
19 | editingState: DataframeEditingState,
20 | collectionService: RxDBCollectionService
21 | ): [Entity[] | undefined, React.Dispatch>] => {
22 | const [entities, setEntities] = useState();
23 | const editingStateRef = useRef(editingState);
24 |
25 | useEffect(() => {
26 | if (
27 | !entities ||
28 | isEmpty(editingState) ||
29 | equal(editingState, editingStateRef.current)
30 | ) {
31 | return;
32 | }
33 | editingStateRef.current = editingState;
34 |
35 | const { added_rows: added, edited_rows: edited, deleted_rows: deleted } = editingState;
36 |
37 | if (!isEmpty(added)) {
38 | const docs = added.map(item => ({
39 | ...item,
40 | id: uuid(),
41 | createdAt: new Date().toISOString(),
42 | last_modified: Date.now(),
43 | }));
44 | if (!docs.length) return;
45 | collectionService.upsertBulk(docs).catch(error => logger.log('upsertBulk', error));
46 | }
47 |
48 | if (!isEmpty(deleted)) {
49 | const ids: string[] = [];
50 | deleted.forEach(rowIndex => {
51 | const entity = entities.at(rowIndex);
52 | if (entity) {
53 | ids.push(entity.id);
54 | }
55 | });
56 | if (!ids.length) return;
57 | collectionService.removeBulk(ids).catch(error => logger.log('removeBulk', error));
58 | }
59 |
60 | if (!isEmpty(edited)) {
61 | const docs = Object.entries(edited).map(([rowIndex, change]) => {
62 | const entity = entities.at(+rowIndex);
63 | return {
64 | ...entity,
65 | ...change,
66 | last_modified: Date.now(),
67 | };
68 | });
69 | if (!docs.length) return;
70 | collectionService.upsertBulk(docs).catch(error => logger.log('upsertBulk', error));
71 | }
72 | }, [editingState, entities, collectionService]);
73 |
74 | return [entities, setEntities];
75 | };
76 |
--------------------------------------------------------------------------------
/decorate-angular-cli.js:
--------------------------------------------------------------------------------
1 | /**
2 | * This file decorates the Angular CLI with the Nx CLI to enable features such as computation caching
3 | * and faster execution of tasks.
4 | *
5 | * It does this by:
6 | *
7 | * - Patching the Angular CLI to warn you in case you accidentally use the undecorated ng command.
8 | * - Symlinking the ng to nx command, so all commands run through the Nx CLI
9 | * - Updating the package.json postinstall script to give you control over this script
10 | *
11 | * The Nx CLI decorates the Angular CLI, so the Nx CLI is fully compatible with it.
12 | * Every command you run should work the same when using the Nx CLI, except faster.
13 | *
14 | * Because of symlinking you can still type `ng build/test/lint` in the terminal. The ng command, in this case,
15 | * will point to nx, which will perform optimizations before invoking ng. So the Angular CLI is always invoked.
16 | * The Nx CLI simply does some optimizations before invoking the Angular CLI.
17 | *
18 | * To opt out of this patch:
19 | * - Replace occurrences of nx with ng in your package.json
20 | * - Remove the script from your postinstall script in your package.json
21 | * - Delete and reinstall your node_modules
22 | */
23 |
24 | const fs = require('fs');
25 | const os = require('os');
26 | const cp = require('child_process');
27 | const isWindows = os.platform() === 'win32';
28 | let output;
29 | try {
30 | output = require('@nrwl/workspace').output;
31 | } catch (e) {
32 | console.warn(
33 | 'Angular CLI could not be decorated to enable computation caching. Please ensure @nrwl/workspace is installed.'
34 | );
35 | process.exit(0);
36 | }
37 |
38 | /**
39 | * Symlink of ng to nx, so you can keep using `ng build/test/lint` and still
40 | * invoke the Nx CLI and get the benefits of computation caching.
41 | */
42 | function symlinkNgCLItoNxCLI() {
43 | try {
44 | const ngPath = './node_modules/.bin/ng';
45 | const nxPath = './node_modules/.bin/nx';
46 | if (isWindows) {
47 | /**
48 | * This is the most reliable way to create symlink-like behavior on Windows.
49 | * Such that it works in all shells and works with npx.
50 | */
51 | ['', '.cmd', '.ps1'].forEach(ext => {
52 | if (fs.existsSync(nxPath + ext))
53 | fs.writeFileSync(ngPath + ext, fs.readFileSync(nxPath + ext));
54 | });
55 | } else {
56 | // If unix-based, symlink
57 | cp.execSync(`ln -sf ./nx ${ngPath}`);
58 | }
59 | } catch (e) {
60 | output.error({
61 | title: 'Unable to create a symlink from the Angular CLI to the Nx CLI:' + e.message,
62 | });
63 | throw e;
64 | }
65 | }
66 |
67 | try {
68 | symlinkNgCLItoNxCLI();
69 | require('@nrwl/cli/lib/decorate-cli').decorateCli();
70 | output.log({
71 | title: 'Angular CLI has been decorated to enable computation caching.',
72 | });
73 | } catch (e) {
74 | output.error({
75 | title: 'Decoration of the Angular CLI did not complete successfully',
76 | });
77 | }
78 |
--------------------------------------------------------------------------------
/packages/rxdb/core/src/lib/service.ts:
--------------------------------------------------------------------------------
1 | import {
2 | RxCollectionExtended as RxCollection,
3 | RxCollectionCreatorExtended,
4 | } from '@ngx-odm/rxdb/config';
5 | import { prepareCollections } from '@ngx-odm/rxdb/prepare';
6 | import { NgxRxdbUtils } from '@ngx-odm/rxdb/utils';
7 | import {
8 | CollectionsOfDatabase,
9 | RxDatabase,
10 | RxDatabaseCreator,
11 | createRxDatabase,
12 | } from 'rxdb';
13 | import { loadRxDBPlugins } from './plugin.loader';
14 |
15 | const { logger } = NgxRxdbUtils;
16 |
17 | /**
18 | * Service for managing a RxDB database instance.
19 | */
20 | export class RxDBService {
21 | private dbInstance!: RxDatabase;
22 | private options!: RxDatabaseCreator;
23 |
24 | get db(): RxDatabase {
25 | // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
26 | return this.dbInstance!;
27 | }
28 |
29 | get dbOptions(): RxDatabaseCreator {
30 | return this.options;
31 | }
32 |
33 | get collections(): { [name: string]: RxCollection } {
34 | return this.db.collections as { [name: string]: RxCollection };
35 | }
36 |
37 | async destroyDb() {
38 | try {
39 | await this.db.remove();
40 | await this.db.destroy();
41 | (this.dbInstance as unknown) = null;
42 | logger.log(`database destroy`);
43 | } catch {
44 | logger.log(`database destroy error`);
45 | }
46 | }
47 |
48 | /**
49 | * Runs via APP_INITIALIZER in app.module.ts
50 | * to ensure the database exists before the angular-app starts up
51 | * @param config
52 | */
53 | async initDb(config: RxDatabaseCreator): Promise {
54 | if (this.dbInstance) {
55 | return;
56 | }
57 | try {
58 | await loadRxDBPlugins(config.options?.plugins);
59 | this.dbInstance = await createRxDatabase(config);
60 | this.options = config;
61 | logger.log(
62 | `created database "${this.db.name}" with config "${JSON.stringify(config)}"`
63 | );
64 |
65 | // optional: can create collections from root config
66 | if (config?.options?.schemas) {
67 | const bulk = await this.initCollections(config.options.schemas);
68 | logger.log(
69 | `created ${Object.keys(bulk).length} collections bulk: ${Object.keys(bulk)}`
70 | );
71 | }
72 | } catch (error) {
73 | logger.log('Error initializing the database:', error);
74 | throw error;
75 | }
76 | }
77 |
78 | /**
79 | * uses bulk `addCollections` method at the end of array
80 | * @param colConfigs
81 | */
82 | async initCollections(colConfigs: {
83 | [name: string]: RxCollectionCreatorExtended;
84 | }): Promise {
85 | // eslint-disable-next-line no-useless-catch
86 | try {
87 | const colCreators = await prepareCollections(colConfigs);
88 | return await this.db.addCollections(colCreators);
89 | } catch (error) {
90 | logger.log('Error initializing collection(s)', error);
91 | throw error;
92 | }
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/packages/rxdb/prepare/src/lib/prepare.plugin.spec.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable @typescript-eslint/ban-ts-comment, @typescript-eslint/no-non-null-assertion */
2 | import { getMockRxCollection } from '@ngx-odm/rxdb/testing';
3 | import { RxCollection, RxCollectionCreator, RxQuery } from 'rxdb';
4 | import { RxDBPreparePlugin } from './prepare.plugin';
5 |
6 | describe('RxDBPreparePlugin', () => {
7 | describe('test hook "createRxCollection:after"', () => {
8 | let collection: RxCollection;
9 | let creator: RxCollectionCreator;
10 | let collectionCount: RxQuery;
11 | let createRxCollectionAfter: any; // RxPluginHooks['after']
12 |
13 | beforeEach(async () => {
14 | collection = await getMockRxCollection();
15 | collectionCount = collection.count();
16 | creator = { options: {} } as RxCollectionCreator;
17 | createRxCollectionAfter = RxDBPreparePlugin.hooks!.createRxCollection!.after!;
18 | });
19 |
20 | it('should not import initial docs if collection is not empty', async () => {
21 | jest.spyOn(collectionCount, 'exec').mockResolvedValue(1);
22 | await createRxCollectionAfter({ collection, creator });
23 | expect(collection.importJSON).not.toHaveBeenCalled();
24 | });
25 |
26 | it('should not import initial docs if initialDocs is empty', async () => {
27 | creator.options.initialDocs = [];
28 | await createRxCollectionAfter({ collection, creator });
29 | expect(collection.importJSON).not.toHaveBeenCalled();
30 | });
31 |
32 | it('should not import initial docs if collection has already been imported', async () => {
33 | jest.spyOn(collection as any, 'getMetadata').mockResolvedValueOnce({
34 | isNewDb: false,
35 | });
36 | await createRxCollectionAfter({ collection, creator });
37 | expect(collection.importJSON).not.toHaveBeenCalled();
38 | });
39 |
40 | it('should import initial docs if collection is empty and initialDocs is not empty', async () => {
41 | creator.options.recreate = true;
42 | creator.options.initialDocs = [{ foo: 'bar' }];
43 | await createRxCollectionAfter({ collection, creator });
44 | expect(collection.importJSON).toHaveBeenCalledWith({
45 | name: collection.name,
46 | schemaHash: expect.any(String),
47 | docs: creator.options.initialDocs,
48 | });
49 | });
50 |
51 | it('should not throw if count fails', async () => {
52 | const error = new Error('count failed');
53 | jest.spyOn(collectionCount, 'exec').mockRejectedValue(error);
54 | expect(
55 | async () => await createRxCollectionAfter({ collection, creator })
56 | ).not.toThrow();
57 | });
58 |
59 | it('should not throw if importJSON fails', async () => {
60 | const error = new Error('importJSON failed');
61 | jest.spyOn(collection, 'importJSON').mockRejectedValue(error);
62 | expect(
63 | async () => await createRxCollectionAfter({ collection, creator })
64 | ).not.toThrow();
65 | });
66 | });
67 | });
68 |
--------------------------------------------------------------------------------
/examples/demo/src/polyfills.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * This file includes polyfills needed by Angular and is loaded before the app.
3 | * You can add your own extra polyfills to this file.
4 | *
5 | * This file is divided into 2 sections:
6 | * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers.
7 | * 2. Application imports. Files imported after ZoneJS that should be loaded before your main
8 | * file.
9 | *
10 | * The current setup is for so-called "evergreen" browsers; the last versions of browsers that
11 | * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera),
12 | * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile.
13 | *
14 | * Learn more in https://angular.io/guide/browser-support
15 | */
16 |
17 | /***************************************************************************************************
18 | * BROWSER POLYFILLS
19 | */
20 |
21 | /** IE11 requires the following for NgClass support on SVG elements */
22 | // import 'classlist.js'; // Run `npm install --save classlist.js`.
23 |
24 | /**
25 | * Web Animations `@angular/platform-browser/animations`
26 | * Only required if AnimationBuilder is used within the application and using IE/Edge or Safari.
27 | * Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0).
28 | */
29 | // import 'web-animations-js'; // Run `npm install --save web-animations-js`.
30 |
31 | /**
32 | * By default, zone.js will patch all possible macroTask and DomEvents
33 | * user can disable parts of macroTask/DomEvents patch by setting following flags
34 | * because those flags need to be set before `zone.js` being loaded, and webpack
35 | * will put import in the top of bundle, so user need to create a separate file
36 | * in this directory (for example: zone-flags.ts), and put the following flags
37 | * into that file, and then add the following code before importing zone.js.
38 | * import './zone-flags';
39 | *
40 | * The flags allowed in zone-flags.ts are listed here.
41 | *
42 | * The following flags will work for all browsers.
43 | *
44 | * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame
45 | * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick
46 | * (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames
47 | *
48 | * in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js
49 | * with the following flag, it will bypass `zone.js` patch for IE/Edge
50 | *
51 | * (window as any).__Zone_enable_cross_context_check = true;
52 | *
53 | */
54 |
55 | /***************************************************************************************************
56 | * Zone JS is required by default for Angular itself.
57 | */
58 | import 'zone.js'; // Included with Angular CLI.
59 |
60 | /***************************************************************************************************
61 | * APPLICATION IMPORTS
62 | */
63 |
--------------------------------------------------------------------------------
/packages/rxdb/collection/src/lib/helpers.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable @typescript-eslint/no-explicit-any */
2 | import type { RxCollectionCreatorExtended } from '@ngx-odm/rxdb/config';
3 | import { NgxRxdbUtils } from '@ngx-odm/rxdb/utils';
4 | import { Observable, OperatorFunction, defer, lastValueFrom, switchMap } from 'rxjs';
5 |
6 | const { debug, isEmptyObject, isFunction } = NgxRxdbUtils;
7 |
8 | type CollectionLike = {
9 | readonly initialized$: Observable;
10 | readonly config: RxCollectionCreatorExtended;
11 | };
12 |
13 | export type ZoneLike = {
14 | run(fn: (...args: any[]) => T, applyThis?: any, applyArgs?: any[]): T;
15 | };
16 |
17 | function isZone(obj: any): obj is ZoneLike {
18 | return !isEmptyObject(obj) && isFunction(obj.run);
19 | }
20 |
21 | /* eslint-disable prettier/prettier */
22 | /**
23 | * Moves observable execution in and out of Angular zone.
24 | * @param zone
25 | */
26 | export function runInZone(zone: ZoneLike): OperatorFunction { // NOSONAR
27 | if (!isZone(zone)) return source => source;
28 |
29 | return source => { // NOSONAR
30 | return new Observable(subscriber => { // NOSONAR
31 | return source.subscribe(
32 | (value: T) => zone.run(() => subscriber.next(value)),
33 | (e: any) => zone.run(() => subscriber.error(e)),
34 | () => zone.run(() => subscriber.complete()) // NOSONAR
35 | );
36 | });
37 | };
38 | }
39 | /* eslint-enable prettier/prettier */
40 |
41 | /**
42 | * Collection method decorator for Observable return type
43 | *
44 | * Ensure the collection is created before the method is called by piping riginal method
45 | * through class init$ observable property which emits when collection is created
46 | */
47 | export function ensureCollection$() {
48 | return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
49 | const originalMethod = descriptor.value;
50 | descriptor.value = function (this: CollectionLike, ...args: any[]) {
51 | return defer(() => {
52 | return this.initialized$.pipe(
53 | switchMap(() => originalMethod.apply(this, args)), // NOSONAR
54 | debug(`collection.${propertyKey}`)
55 | );
56 | });
57 | };
58 | return descriptor;
59 | };
60 | }
61 |
62 | /**
63 | * Collection method decorator for Promise return type
64 | *
65 | * Ensure the collection is created before the method is called by piping riginal method
66 | * through class init$ observable property which emits when collection is created
67 | */
68 | export function ensureCollection() {
69 | return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
70 | const originalMethod = descriptor.value;
71 | descriptor.value = async function (this: CollectionLike, ...args: any[]) {
72 | await lastValueFrom(this.initialized$).catch(() => {
73 | // eslint-disable-next-line prettier/prettier
74 | throw new Error(`Collection "${this.config.name}" was not initialized. Please check RxDB errors.`);
75 | });
76 |
77 | return originalMethod.apply(this, args);
78 | };
79 | return descriptor;
80 | };
81 | }
82 |
--------------------------------------------------------------------------------
/nx.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "./node_modules/nx/schemas/nx-schema.json",
3 | "workspaceLayout": {
4 | "appsDir": "examples",
5 | "libsDir": "packages",
6 | "projectNameAndRootFormat": "as-provided"
7 | },
8 | "affected": {
9 | "defaultBase": "origin/master"
10 | },
11 | "release": {
12 | "projects": ["packages/*"],
13 | "projectChangelogs": {
14 | "createRelease": "github"
15 | },
16 | "version": {
17 | "generatorOptions": {
18 | "preid": "dev"
19 | }
20 | }
21 | },
22 | "targetDefaults": {
23 | "build": {
24 | "dependsOn": ["^build"],
25 | "inputs": ["production", "^production"],
26 | "cache": true
27 | },
28 | "lint": {
29 | "inputs": [
30 | "default",
31 | "{workspaceRoot}/.eslintrc.json",
32 | "{workspaceRoot}/.eslintignore",
33 | "{workspaceRoot}/eslint.config.js"
34 | ],
35 | "cache": true
36 | },
37 | "@nx/jest:jest": {
38 | "inputs": ["default", "^production", "{workspaceRoot}/jest.preset.js"],
39 | "cache": true,
40 | "options": {
41 | "passWithNoTests": true
42 | },
43 | "configurations": {
44 | "ci": {
45 | "ci": true,
46 | "codeCoverage": true
47 | }
48 | }
49 | }
50 | },
51 | "namedInputs": {
52 | "default": ["{projectRoot}/**/*", "sharedGlobals"],
53 | "production": [
54 | "default",
55 | "!{projectRoot}/**/?(*.)+(spec|test).[jt]s?(x)?(.snap)",
56 | "!{projectRoot}/tsconfig.spec.json",
57 | "!{projectRoot}/jest.config.[jt]s",
58 | "!{projectRoot}/src/test-setup.[jt]s",
59 | "!{projectRoot}/test-setup.[jt]s",
60 | "!{projectRoot}/.eslintrc.json",
61 | "!{projectRoot}/eslint.config.js"
62 | ],
63 | "sharedGlobals": []
64 | },
65 | "generators": {
66 | "@nx/angular:application": {
67 | "style": "css",
68 | "linter": "eslint",
69 | "unitTestRunner": "jest",
70 | "e2eTestRunner": "none"
71 | },
72 | "@nx/angular:library": {
73 | "linter": "eslint",
74 | "unitTestRunner": "jest"
75 | },
76 | "@nx/angular:component": {
77 | "style": "css"
78 | },
79 | "@nx/react": {
80 | "library": {
81 | "style": "css",
82 | "linter": "eslint",
83 | "unitTestRunner": "jest"
84 | },
85 | "application": {
86 | "babel": true,
87 | "style": "css",
88 | "linter": "eslint",
89 | "bundler": "vite"
90 | },
91 | "component": {
92 | "style": "css"
93 | }
94 | }
95 | },
96 | "plugins": [
97 | {
98 | "plugin": "@nx/eslint/plugin",
99 | "options": {
100 | "targetName": "lint"
101 | }
102 | },
103 | {
104 | "plugin": "@nx/vite/plugin",
105 | "options": {
106 | "buildTargetName": "build",
107 | "previewTargetName": "preview",
108 | "testTargetName": "test",
109 | "serveTargetName": "serve",
110 | "serveStaticTargetName": "serve-static"
111 | }
112 | }
113 | ]
114 | }
115 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Covenant Code of Conduct
2 |
3 | ## Our Pledge
4 |
5 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
6 |
7 | ## Our Standards
8 |
9 | Examples of behavior that contributes to creating a positive environment include:
10 |
11 | - Using welcoming and inclusive language
12 | - Being respectful of differing viewpoints and experiences
13 | - Gracefully accepting constructive criticism
14 | - Focusing on what is best for the community
15 | - Showing empathy towards other community members
16 |
17 | Examples of unacceptable behavior by participants include:
18 |
19 | - The use of sexualized language or imagery and unwelcome sexual attention or advances
20 | - Trolling, insulting/derogatory comments, and personal or political attacks
21 | - Public or private harassment
22 | - Publishing others' private information, such as a physical or electronic address, without explicit permission
23 | - Other conduct which could reasonably be considered inappropriate in a professional setting
24 |
25 | ## Our Responsibilities
26 |
27 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
28 |
29 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
30 |
31 | ## Scope
32 |
33 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
34 |
35 | ## Enforcement
36 |
37 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at worrydontcom@gmail.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
38 |
39 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
40 |
41 | ## Attribution
42 |
43 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]
44 |
45 | [homepage]: http://contributor-covenant.org
46 | [version]: http://contributor-covenant.org/version/1/4/
47 |
--------------------------------------------------------------------------------
/packages/rxdb/query-params/src/query-params.plugin.spec.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable @typescript-eslint/no-unused-vars */
2 | /* @typescript-eslint/no-non-null-assertion */
3 | import { RxCollectionExtended as RxCollection } from '@ngx-odm/rxdb/config';
4 | import {
5 | TEST_FEATURE_CONFIG_1,
6 | TestDocType,
7 | getMockRxCollection,
8 | } from '@ngx-odm/rxdb/testing';
9 | import type { MangoQuery, RxCollectionCreator, RxPlugin, RxQuery } from 'rxdb';
10 | import { Observable, Subject, take } from 'rxjs';
11 | import { RxDBPUseQueryParamsPlugin } from './query-params.plugin';
12 |
13 | describe('RxDBPUseQueryParamsPlugin', () => {
14 | describe('test proto extension', () => {
15 | let collection: RxCollection;
16 | let creator: RxCollectionCreator;
17 | let plugin: RxPlugin;
18 | const mockUrlStream$ = new Subject();
19 |
20 | beforeEach(async () => {
21 | const colConfig = TEST_FEATURE_CONFIG_1;
22 | colConfig.options.useQueryParams = true;
23 | plugin = RxDBPUseQueryParamsPlugin;
24 | collection = (await getMockRxCollection(colConfig)) as RxCollection;
25 | });
26 |
27 | it('should add properties & methods for query-params', async () => {
28 | expect(collection.queryParamsInit).toBeDefined();
29 | expect(collection.queryParams!.$).toBeInstanceOf(Observable);
30 | expect(collection.queryParams!.set).toBeDefined();
31 | expect(collection.queryParams!.patch).toBeDefined();
32 | });
33 |
34 | it('should properly intialize usage of query-params', async () => {
35 | const startUrl =
36 | 'http://localhost:4200/todos?limit=1&selector=%7B%22completed%22:%7B%22$eq%22:true%7D%7D&sort=%5B%7B%22createdAt%22:%22desc%22%7D%5D';
37 | collection.queryParamsInit!(mockUrlStream$, jest.fn());
38 | mockUrlStream$.next(startUrl);
39 | const expectedQueryParams = {
40 | selector: {
41 | completed: {
42 | $eq: true,
43 | },
44 | },
45 | sort: [{ createdAt: 'desc' }],
46 | limit: 1,
47 | skip: undefined,
48 | };
49 | const queryParamsValue = await collection.queryParams!.$.pipe(take(1)).toPromise();
50 | expect(queryParamsValue).toEqual(expectedQueryParams);
51 | });
52 | it('should properly set query-params', async () => {
53 | const nextUrl =
54 | 'http://localhost:4200/todos?limit=2&sort=%5B%7B%22createdAt%22:%22asc%22%7D%5D&skip=0';
55 |
56 | collection.queryParamsInit!(mockUrlStream$, jest.fn());
57 | const newQueryParams: MangoQuery = {
58 | selector: undefined,
59 | sort: [{ createdAt: 'asc' }],
60 | limit: 2,
61 | skip: 0,
62 | };
63 | collection.queryParams!.set(newQueryParams);
64 | mockUrlStream$.next(nextUrl);
65 | const queryParamsValue = await collection.queryParams!.$.pipe(take(1)).toPromise();
66 | expect(queryParamsValue).toEqual(newQueryParams);
67 | });
68 | it('should properly patch query-params', async () => {
69 | const startUrl = 'http://localhost:4200/todos?limit=0';
70 | const nextUrl = 'http://localhost:4200/todos?limit=1&skip=1';
71 | mockUrlStream$.next(startUrl);
72 | collection.queryParamsInit!(mockUrlStream$, jest.fn());
73 | const newQueryParams: MangoQuery = {
74 | limit: 1,
75 | skip: 1,
76 | };
77 | collection.queryParams!.patch(newQueryParams);
78 | mockUrlStream$.next(nextUrl);
79 | const queryParamsValue = await collection.queryParams!.$.pipe(take(1)).toPromise();
80 | expect(queryParamsValue).toMatchObject(newQueryParams);
81 | });
82 | });
83 | });
84 |
--------------------------------------------------------------------------------
/examples/demo/project.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "demo",
3 | "$schema": "../../node_modules/nx/schemas/project-schema.json",
4 | "projectType": "application",
5 | "prefix": "demo",
6 | "sourceRoot": "examples/demo/src",
7 | "tags": [],
8 | "targets": {
9 | "build": {
10 | "executor": "@angular-devkit/build-angular:browser",
11 | "outputs": ["{options.outputPath}"],
12 | "options": {
13 | "outputPath": "dist/demo",
14 | "index": "examples/demo/src/index.html",
15 | "main": "examples/demo/src/main.ts",
16 | "polyfills": ["zone.js"],
17 | "tsConfig": "examples/demo/tsconfig.app.json",
18 | "assets": ["examples/demo/src/favicon.ico", "examples/demo/src/assets"],
19 | "styles": [
20 | "node_modules/todomvc-common/base.css",
21 | "node_modules/todomvc-app-css/index.css"
22 | ],
23 | "scripts": []
24 | },
25 | "configurations": {
26 | "production": {
27 | "fileReplacements": [
28 | {
29 | "replace": "examples/shared/environment.ts",
30 | "with": "examples/shared/environment.prod.ts"
31 | }
32 | ],
33 | "budgets": [
34 | {
35 | "type": "initial",
36 | "maximumWarning": "500kb",
37 | "maximumError": "1mb"
38 | },
39 | {
40 | "type": "anyComponentStyle",
41 | "maximumWarning": "2kb",
42 | "maximumError": "4kb"
43 | }
44 | ],
45 | "outputHashing": "all",
46 | "vendorChunk": true
47 | },
48 | "development": {
49 | "buildOptimizer": false,
50 | "optimization": false,
51 | "vendorChunk": true,
52 | "extractLicenses": false,
53 | "sourceMap": true,
54 | "namedChunks": true
55 | }
56 | },
57 | "defaultConfiguration": "production"
58 | },
59 | "serve": {
60 | "executor": "@angular-devkit/build-angular:dev-server",
61 | "configurations": {
62 | "production": {
63 | "buildTarget": "demo:build:production"
64 | },
65 | "development": {
66 | "buildTarget": "demo:build:development",
67 | "proxyConfig": "examples/demo/proxy.conf.js",
68 | "verbose": true
69 | }
70 | },
71 | "defaultConfiguration": "development"
72 | },
73 | "extract-i18n": {
74 | "executor": "@angular-devkit/build-angular:extract-i18n",
75 | "options": {
76 | "buildTarget": "demo:build"
77 | }
78 | },
79 | "lint": {
80 | "executor": "@nx/eslint:lint",
81 | "outputs": ["{options.outputFile}"]
82 | },
83 | "test": {
84 | "executor": "@nx/jest:jest",
85 | "outputs": ["{workspaceRoot}/coverage/{projectRoot}"],
86 | "options": {
87 | "jestConfig": "examples/demo/jest.config.ts",
88 | "passWithNoTests": true
89 | },
90 | "configurations": {
91 | "ci": {
92 | "ci": true,
93 | "codeCoverage": true
94 | }
95 | }
96 | },
97 | "file-server": {
98 | "executor": "@nrwl/web:file-server",
99 | "options": {
100 | "buildTarget": "demo:build:production",
101 | "port": 4201,
102 | "host": "0.0.0.0",
103 | "proxyUrl": "http://localhost:4201?",
104 | "watch": true
105 | }
106 | },
107 | "version": {
108 | "executor": "@jscutlery/semver:version",
109 | "options": {
110 | "preset": "conventional"
111 | }
112 | }
113 | }
114 | }
115 |
--------------------------------------------------------------------------------
/packages/streamlit-rxdb-dataframe/example.py:
--------------------------------------------------------------------------------
1 | import json
2 | import os
3 | from typing import Dict, List
4 | import streamlit as st
5 | from streamlit.runtime.caching import cache_data
6 | from rxdb_dataframe import (
7 | RXDB_COLLECTION_EDITOR_KEY,
8 | RxCollectionCreator,
9 | RxDBSessionState,
10 | rxdb_dataframe,
11 | )
12 |
13 | # from pandas.api.types import ( is_bool_dtype, is_categorical_dtype, is_datetime64_any_dtype, is_numeric_dtype, is_object_dtype, ) # noqa: E501
14 |
15 | current_dir = os.path.dirname(os.path.abspath(__file__))
16 | data_dir = os.path.join(current_dir, "rxdb_dataframe/frontend/public/assets/data")
17 |
18 | collection_name = "todo"
19 | todoSchema: Dict = json.load(open(os.path.join(data_dir, "todo.schema.json")))
20 | initial_docs: List = json.load(open(os.path.join(data_dir, "col.dump.json")))["docs"]
21 | collection_config: RxCollectionCreator = {
22 | "name": collection_name,
23 | "schema": todoSchema, # to auto load schema from remote url pass None
24 | "localDocuments": True,
25 | "options": {
26 | # 'schemaUrl': 'assets/data/todo.schema.json',
27 | "initialDocs": initial_docs,
28 | "recreate": False,
29 | },
30 | }
31 |
32 |
33 | state = RxDBSessionState()
34 | column_config = state.column_config
35 | query = state.query
36 |
37 |
38 | def on_change_dataframe(rxdb_state: RxDBSessionState):
39 | print("RxDBDataframe component on_change call")
40 | print("collection.info()", rxdb_state.info)
41 |
42 |
43 | @cache_data
44 | def apply_row_style(row):
45 | return (
46 | ["color:green"] * len(row) if row.completed else ["color:grey"] * len(row) # default color
47 | ) # noqa: E501
48 |
49 |
50 | display = st.radio(
51 | "Display RxDB collection as:",
52 | options=["dataframe", "data_editor", "table"],
53 | horizontal=True,
54 | )
55 | filter = st.radio(
56 | label="Filter data using RxDB **RxQuery**",
57 | help="MangoQuery syntax",
58 | label_visibility="visible",
59 | options=["all", "active", "completed"],
60 | horizontal=True,
61 | )
62 |
63 | if filter == "active":
64 | query = {"selector": {"completed": {"$eq": False}}}
65 | elif filter == "completed":
66 | query = {"selector": {"completed": {"$eq": True}}}
67 | else:
68 | query = {"selector": {}}
69 |
70 | df = rxdb_dataframe(
71 | collection_config,
72 | query=query,
73 | with_rev=False,
74 | on_change=on_change_dataframe,
75 | )
76 |
77 | if display == "data_editor":
78 | try:
79 | st.data_editor(
80 | df.copy().style.apply(apply_row_style, axis=1),
81 | use_container_width=True,
82 | hide_index=True,
83 | column_config=column_config,
84 | column_order=["title", "completed", "createdAt"],
85 | num_rows="dynamic",
86 | key=RXDB_COLLECTION_EDITOR_KEY,
87 | )
88 | except Exception as e:
89 | st.error(f"An error occurred: {str(e)}")
90 | elif display == "dataframe":
91 | try:
92 | st.dataframe(
93 | df.style.apply(apply_row_style, axis=1),
94 | use_container_width=True,
95 | hide_index=True,
96 | column_config=column_config,
97 | column_order=["title", "completed", "createdAt"],
98 | )
99 | except Exception as e:
100 | st.error(f"An error occurred: {str(e)}")
101 | else:
102 | try:
103 | st.table(
104 | df.style.apply(apply_row_style, axis=1),
105 | )
106 | except Exception as e:
107 | st.error(f"An error occurred: {str(e)}")
108 |
109 |
110 | with st.sidebar:
111 | st.write("RxDB Collection Config")
112 | st.json(collection_config, expanded=False)
113 |
114 | st.write("RsDB Session State:")
115 | st.json(st.session_state, expanded=False)
116 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing to @ngx-odm
2 |
3 | :+1::tada: First off, thanks for taking the time to contribute! :tada::+1:
4 |
5 | The following is a set of guidelines for contributing to @ngx-odm and its packages.
6 |
7 | #### Table Of Contents
8 |
9 | [I don't want to read this whole thing, I just have a question!!!](#i-dont-want-to-read-this-whole-thing-i-just-have-a-question)
10 |
11 | [How can I help?](#how-can-I-help?)
12 |
13 | [Developing](#developing)
14 |
15 | [Building](#building)
16 |
17 | - [Linking local version](#linking)
18 |
19 | [Publish new version](#publishing-new-version)
20 |
21 | - [Dev builds](#dev-builds)
22 | - [Beta builds](#beta-builds)
23 | - [Release builds](#release-builds)
24 |
25 | [Commit Message Guidelines](#commit)
26 |
27 | ## I don't want to read this whole thing I just have a question!!!
28 |
29 | > **Note:** Please don't file an issue to ask a question.
30 |
31 | - Read Rxdb [docs](https://rxdb.info)
32 | - Ask a question on [stackoverflow](https://stackoverflow.com/questions/tagged/rxdb).
33 |
34 | # How can I help?
35 |
36 | Check the [issues](https://github.com/voznik/ngx-odm/issues) where we have labels for help wanted.
37 |
38 | # Developing
39 |
40 | Start by installing all dependencies
41 | ...
42 |
43 |
44 |
45 | # Commit Message Guidelines
46 |
47 | We have very precise rules over how our git commit messages can be formatted. This leads to **more
48 | readable messages** that are easy to follow when looking through the **project history**. But also,
49 | we use the git commit messages to **generate the Angular change log**.
50 |
51 | ### Commit Message Format
52 |
53 | Each commit message consists of a **header**, a **body** and a **footer**. The header has a special
54 | format that includes a **type**, a **scope** and a **subject**:
55 |
56 | ```
57 | ():
58 |
59 |
60 |
61 |