├── .editorconfig
├── .gitignore
├── README.md
├── angular.json
├── browserslist
├── e2e
├── protractor.conf.js
├── src
│ ├── app.e2e-spec.ts
│ └── app.po.ts
└── tsconfig.json
├── karma.conf.js
├── package-lock.json
├── package.json
├── projects
└── ng-reactive
│ ├── README.md
│ ├── karma.conf.js
│ ├── ng-package.json
│ ├── package.json
│ ├── src
│ ├── lib
│ │ ├── metadata.ts
│ │ ├── ng-reactive.spec.ts
│ │ ├── ng-reactive.ts
│ │ └── util.ts
│ ├── public-api.ts
│ └── test.ts
│ ├── tsconfig.lib.json
│ ├── tsconfig.spec.json
│ └── tslint.json
├── src
├── app
│ ├── app.component.css
│ ├── app.component.html
│ ├── app.component.spec.ts
│ ├── app.component.ts
│ └── app.module.ts
├── assets
│ └── .gitkeep
├── environments
│ ├── environment.prod.ts
│ └── environment.ts
├── favicon.ico
├── index.html
├── main.ts
├── polyfills.ts
├── styles.css
└── test.ts
├── tsconfig.app.json
├── tsconfig.json
├── tsconfig.spec.json
└── tslint.json
/.editorconfig:
--------------------------------------------------------------------------------
1 | # Editor configuration, see https://editorconfig.org
2 | root = true
3 |
4 | [*]
5 | charset = utf-8
6 | indent_style = space
7 | indent_size = 2
8 | insert_final_newline = true
9 | trim_trailing_whitespace = true
10 |
11 | [*.md]
12 | max_line_length = off
13 | trim_trailing_whitespace = false
14 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See http://help.github.com/ignore-files/ for more about ignoring files.
2 |
3 | # compiled output
4 | /dist
5 | /tmp
6 | /out-tsc
7 | # Only exists if Bazel was run
8 | /bazel-out
9 |
10 | # dependencies
11 | /node_modules
12 |
13 | # profiling files
14 | chrome-profiler-events.json
15 | speed-measure-plugin.json
16 |
17 | # IDEs and editors
18 | /.idea
19 | .project
20 | .classpath
21 | .c9/
22 | *.launch
23 | .settings/
24 | *.sublime-workspace
25 |
26 | # IDE - VSCode
27 | .vscode/*
28 | !.vscode/settings.json
29 | !.vscode/tasks.json
30 | !.vscode/launch.json
31 | !.vscode/extensions.json
32 | .history/*
33 |
34 | # misc
35 | /.sass-cache
36 | /connect.lock
37 | /coverage
38 | /libpeerconnection.log
39 | npm-debug.log
40 | yarn-error.log
41 | testem.log
42 | /typings
43 |
44 | # System Files
45 | .DS_Store
46 | Thumbs.db
47 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # ng-reactive
2 |
3 | Reactive utility for Angular, embraces binding from RxJS observable to component property.
4 |
5 | ## Install
6 |
7 | ```bash
8 | npm install ng-reactive
9 | ```
10 |
11 | ## Usage
12 |
13 | Eliminate async pipe and subscription process:
14 |
15 | ```typescript
16 | import { bind, reset, state, unbind, updateOn, viewUpdate, Reactive, StateChanges } from 'ng-reactive'
17 | import { interval, of, Observable } from 'rxjs'
18 | import { map } from 'rxjs/operators'
19 | import { AccountService, User } from './account.service'
20 | import { HeartbeatService } from './heartbeat.service'
21 |
22 | @Component({
23 | template: `
24 |
25 |
Inside reactive component
26 | Hello {{ username }}, it's {{ time | date:'medium' }} now.
27 | 0">
28 | Items:
29 |
32 |
33 | `,
34 | })
35 | class HelloComponent extends Reactive {
36 | // Define inputs as before
37 | @Input() id: string
38 | @Input() password: string
39 |
40 | // Define a set of reactive states with their default values
41 | username = state('Anonymous')
42 | items: string[] = state([])
43 | time = state(new Date())
44 |
45 | constructor(
46 | injector: Injector,
47 | private accountService: AccountService,
48 | private heartbeatService: HeartbeatService,
49 | ) {
50 | // Passing injector for automatic marking dirty
51 | super(injector)
52 | }
53 |
54 | // Implement `update()` method based on changes and whether it's first run
55 | update(changes: StateChanges, first: boolean) {
56 | // Execute only in first run
57 | if (first) {
58 | // Bind an RxJS observable to a reactive state
59 | bind(this.time, interval(1000).pipe(map(() => new Date())))
60 | }
61 |
62 | // Execute whenever the `id` or `password` input changes
63 | if (updateOn(changes.id, changes.password)) {
64 | if (this.id != null && this.password != null) {
65 | // Binding to a constant value
66 | bind(this.username, of('Loading...'))
67 |
68 | const user$ = this.doLogin(this.id, this.password)
69 | // Previous subscription will be unsubscribed automatically
70 | bind(this.username, user$.pipe(map(x => x.name)))
71 | bind(this.items, user$.pipe(map(x => x.items)))
72 | } else {
73 | // Unbind a reactive state
74 | unbind(this.username)
75 | unbind(this.items)
76 |
77 | // Reset a reactive state to its default value
78 | reset(this.username)
79 | // Imperatively change a reactive state
80 | // Array is mutable and the default value may have been polluted
81 | this.items = []
82 | }
83 | }
84 |
85 | // Execute whenever the `username` reactive state changes
86 | // Both inputs and reactive states are tracked
87 | if (updateOn(changes.username)) {
88 | // Schedule an operation after view updated
89 | viewUpdate(() => {
90 | // Operation depends on DOM
91 | this.someViewOperation()
92 | })
93 | }
94 |
95 | // Execute whenever the `time` reactive state changes or in the first run
96 | if (updateOn(changes.time) || first) {
97 | this.heartbeatService.send()
98 | }
99 | }
100 |
101 | // Example method for observable handling
102 | private doLogin(id: string, password: string): Observable {
103 | return this.accountService.login(this.id, this.password)
104 | }
105 |
106 | // Example method for side effects
107 | private someViewOperation() {
108 | const $greeting = document.querySelector('#greeting')
109 | const result = $greeting.textContent.indexOf(this.username) > 0
110 | if (result) {
111 | console.log(`View operation done`)
112 | } else {
113 | console.warn(`View operation failed`)
114 | }
115 | }
116 | }
117 | ```
118 |
119 | Live example available at [StackBlitz](https://stackblitz.com/edit/angular-gtufmp?file=src%2Fapp%2Fhello.component.ts).
120 |
121 | ## Legacy Mode
122 |
123 | If someone don't want or cannot use base class, then it can also be combined with plain Angular components:
124 |
125 | ```typescript
126 | import { bind, state, unbind, updateOn, viewUpdate, Reactive, StateChanges } from 'ng-reactive'
127 |
128 | @Component()
129 | class HelloComponent {
130 | @Input() foo: string
131 |
132 | bar = state(0)
133 | baz = state(true)
134 |
135 | constructor(private injector: Injector) {}
136 |
137 | ngOnChanges(changes: SimpleChanges) {
138 | if (changes.foo) {
139 | // Use `bind()` or `unbind()` at any time
140 | bind(this.bar, this.makeUseOf(this.foo))
141 | }
142 | }
143 |
144 | ngOnInit() {
145 | // Remember to call `init()` in `OnInit` hook
146 | init(this, this.injector)
147 |
148 | bind(this.baz, someDataSource$)
149 | }
150 |
151 | ngOnDestroy() {
152 | // Remember to call `deinit()` in `OnDestroy` hook
153 | deinit(this)
154 | }
155 |
156 | private makeUseOf(foo: string): Observable {
157 | // ...
158 | }
159 | }
160 | ```
161 |
162 | Note, `updateOn()` and `viewUpdate()` cannot be used without the base class.
163 |
164 | ## Cleanup
165 |
166 | An observable is self-disposable, just make sure the finalization exists when making that data source.
167 |
168 | ```typescript
169 | // Setup builtin cleanup logic
170 | const dataSource = new Observable((observable) => {
171 | // ...
172 | return () => {
173 | additionalCleanupLogic()
174 | }
175 | })
176 |
177 | // Setup extra cleanup logic
178 | const dataSource = someObservable.pipe(
179 | finalize(() => {
180 | additionalCleanupLogic()
181 | })
182 | )
183 | ```
184 |
185 | ## Caveat
186 |
187 | The `OnChanges` hook in Angular uses non-minified property name, and the `StateChanges` object bases on it.
188 | When using Closure compiler advanced mode or other property-mangling tool, the input names need to be literal but reactive state names need to be identifier, like:
189 |
190 | ```typescript
191 | update(changes: StateChanges, first: boolean) {
192 | if (updateOn(changes['someInput'])) {
193 | // ...
194 | }
195 |
196 | if (updateOn(changes.someState)) {
197 | // ...
198 | }
199 | }
200 | ```
201 |
202 | Also need to make sure input name are not too short (which could conflict with other minified names).
203 |
204 | Hopefully not much people are doing property mangling outside Google.
205 |
--------------------------------------------------------------------------------
/angular.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "./node_modules/@angular/cli/lib/config/schema.json",
3 | "version": 1,
4 | "newProjectRoot": "projects",
5 | "projects": {
6 | "ng-reactive-workspace": {
7 | "projectType": "application",
8 | "schematics": {},
9 | "root": "",
10 | "sourceRoot": "src",
11 | "prefix": "app",
12 | "architect": {
13 | "build": {
14 | "builder": "@angular-devkit/build-angular:browser",
15 | "options": {
16 | "outputPath": "dist/ng-reactive-workspace",
17 | "index": "src/index.html",
18 | "main": "src/main.ts",
19 | "polyfills": "src/polyfills.ts",
20 | "tsConfig": "tsconfig.app.json",
21 | "aot": false,
22 | "assets": [
23 | "src/favicon.ico",
24 | "src/assets"
25 | ],
26 | "styles": [
27 | "src/styles.css"
28 | ],
29 | "scripts": []
30 | },
31 | "configurations": {
32 | "production": {
33 | "fileReplacements": [
34 | {
35 | "replace": "src/environments/environment.ts",
36 | "with": "src/environments/environment.prod.ts"
37 | }
38 | ],
39 | "optimization": true,
40 | "outputHashing": "all",
41 | "sourceMap": false,
42 | "extractCss": true,
43 | "namedChunks": false,
44 | "aot": true,
45 | "extractLicenses": true,
46 | "vendorChunk": false,
47 | "buildOptimizer": true,
48 | "budgets": [
49 | {
50 | "type": "initial",
51 | "maximumWarning": "2mb",
52 | "maximumError": "5mb"
53 | }
54 | ]
55 | }
56 | }
57 | },
58 | "serve": {
59 | "builder": "@angular-devkit/build-angular:dev-server",
60 | "options": {
61 | "browserTarget": "ng-reactive-workspace:build"
62 | },
63 | "configurations": {
64 | "production": {
65 | "browserTarget": "ng-reactive-workspace:build:production"
66 | }
67 | }
68 | },
69 | "extract-i18n": {
70 | "builder": "@angular-devkit/build-angular:extract-i18n",
71 | "options": {
72 | "browserTarget": "ng-reactive-workspace:build"
73 | }
74 | },
75 | "test": {
76 | "builder": "@angular-devkit/build-angular:karma",
77 | "options": {
78 | "main": "src/test.ts",
79 | "polyfills": "src/polyfills.ts",
80 | "tsConfig": "tsconfig.spec.json",
81 | "karmaConfig": "karma.conf.js",
82 | "assets": [
83 | "src/favicon.ico",
84 | "src/assets"
85 | ],
86 | "styles": [
87 | "src/styles.css"
88 | ],
89 | "scripts": []
90 | }
91 | },
92 | "lint": {
93 | "builder": "@angular-devkit/build-angular:tslint",
94 | "options": {
95 | "tsConfig": [
96 | "tsconfig.app.json",
97 | "tsconfig.spec.json",
98 | "e2e/tsconfig.json"
99 | ],
100 | "exclude": [
101 | "**/node_modules/**"
102 | ]
103 | }
104 | },
105 | "e2e": {
106 | "builder": "@angular-devkit/build-angular:protractor",
107 | "options": {
108 | "protractorConfig": "e2e/protractor.conf.js",
109 | "devServerTarget": "ng-reactive-workspace:serve"
110 | },
111 | "configurations": {
112 | "production": {
113 | "devServerTarget": "ng-reactive-workspace:serve:production"
114 | }
115 | }
116 | }
117 | }
118 | },
119 | "ng-reactive": {
120 | "projectType": "library",
121 | "root": "projects/ng-reactive",
122 | "sourceRoot": "projects/ng-reactive/src",
123 | "prefix": "lib",
124 | "architect": {
125 | "build": {
126 | "builder": "@angular-devkit/build-ng-packagr:build",
127 | "options": {
128 | "tsConfig": "projects/ng-reactive/tsconfig.lib.json",
129 | "project": "projects/ng-reactive/ng-package.json"
130 | }
131 | },
132 | "test": {
133 | "builder": "@angular-devkit/build-angular:karma",
134 | "options": {
135 | "main": "projects/ng-reactive/src/test.ts",
136 | "tsConfig": "projects/ng-reactive/tsconfig.spec.json",
137 | "karmaConfig": "projects/ng-reactive/karma.conf.js"
138 | }
139 | },
140 | "lint": {
141 | "builder": "@angular-devkit/build-angular:tslint",
142 | "options": {
143 | "tsConfig": [
144 | "projects/ng-reactive/tsconfig.lib.json",
145 | "projects/ng-reactive/tsconfig.spec.json"
146 | ],
147 | "exclude": [
148 | "**/node_modules/**"
149 | ]
150 | }
151 | }
152 | }
153 | }},
154 | "defaultProject": "ng-reactive-workspace"
155 | }
156 |
--------------------------------------------------------------------------------
/browserslist:
--------------------------------------------------------------------------------
1 | # This file is used by the build system to adjust CSS and JS output to support the specified browsers below.
2 | # For additional information regarding the format and rule options, please see:
3 | # https://github.com/browserslist/browserslist#queries
4 |
5 | # You can see what browsers were selected by your queries by running:
6 | # npx browserslist
7 |
8 | > 0.5%
9 | last 2 versions
10 | Firefox ESR
11 | not dead
12 | not IE 9-11 # For IE 9-11 support, remove 'not'.
--------------------------------------------------------------------------------
/e2e/protractor.conf.js:
--------------------------------------------------------------------------------
1 | // @ts-check
2 | // Protractor configuration file, see link for more information
3 | // https://github.com/angular/protractor/blob/master/lib/config.ts
4 |
5 | const { SpecReporter } = require('jasmine-spec-reporter');
6 |
7 | /**
8 | * @type { import("protractor").Config }
9 | */
10 | exports.config = {
11 | allScriptsTimeout: 11000,
12 | specs: [
13 | './src/**/*.e2e-spec.ts'
14 | ],
15 | capabilities: {
16 | 'browserName': 'chrome'
17 | },
18 | directConnect: true,
19 | baseUrl: 'http://localhost:4200/',
20 | framework: 'jasmine',
21 | jasmineNodeOpts: {
22 | showColors: true,
23 | defaultTimeoutInterval: 30000,
24 | print: function() {}
25 | },
26 | onPrepare() {
27 | require('ts-node').register({
28 | project: require('path').join(__dirname, './tsconfig.json')
29 | });
30 | jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } }));
31 | }
32 | };
--------------------------------------------------------------------------------
/e2e/src/app.e2e-spec.ts:
--------------------------------------------------------------------------------
1 | import { browser, logging } from 'protractor'
2 | import { AppPage } from './app.po'
3 |
4 | describe('workspace-project App', () => {
5 | let page: AppPage
6 |
7 | beforeEach(() => {
8 | page = new AppPage()
9 | })
10 |
11 | it('should display welcome message', () => {
12 | page.navigateTo()
13 | expect(page.getTitleText()).toEqual('Welcome to ng-reactive!')
14 | })
15 |
16 | afterEach(async () => {
17 | // Assert that there are no errors emitted from the browser
18 | const logs = await browser.manage().logs().get(logging.Type.BROWSER)
19 | expect(logs).not.toContain(jasmine.objectContaining({
20 | level: logging.Level.SEVERE,
21 | } as logging.Entry))
22 | })
23 | })
24 |
--------------------------------------------------------------------------------
/e2e/src/app.po.ts:
--------------------------------------------------------------------------------
1 | import { browser, by, element } from 'protractor'
2 |
3 | export class AppPage {
4 | navigateTo() {
5 | return browser.get(browser.baseUrl) as Promise
6 | }
7 |
8 | getTitleText() {
9 | return element(by.css('app-root h1')).getText() as Promise
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/e2e/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../tsconfig.json",
3 | "compilerOptions": {
4 | "outDir": "../out-tsc/e2e",
5 | "module": "commonjs",
6 | "target": "es5",
7 | "types": [
8 | "jasmine",
9 | "jasminewd2",
10 | "node"
11 | ]
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/karma.conf.js:
--------------------------------------------------------------------------------
1 | // Karma configuration file, see link for more information
2 | // https://karma-runner.github.io/1.0/config/configuration-file.html
3 |
4 | module.exports = function (config) {
5 | config.set({
6 | basePath: '',
7 | frameworks: ['jasmine', '@angular-devkit/build-angular'],
8 | plugins: [
9 | require('karma-jasmine'),
10 | require('karma-chrome-launcher'),
11 | require('karma-jasmine-html-reporter'),
12 | require('karma-coverage-istanbul-reporter'),
13 | require('@angular-devkit/build-angular/plugins/karma')
14 | ],
15 | client: {
16 | clearContext: false // leave Jasmine Spec Runner output visible in browser
17 | },
18 | coverageIstanbulReporter: {
19 | dir: require('path').join(__dirname, './coverage/ng-reactive'),
20 | reports: ['html', 'lcovonly', 'text-summary'],
21 | fixWebpackSourcePaths: true
22 | },
23 | reporters: ['progress', 'kjhtml'],
24 | port: 9876,
25 | colors: true,
26 | logLevel: config.LOG_INFO,
27 | autoWatch: true,
28 | browsers: ['Chrome'],
29 | singleRun: false,
30 | restartOnFileChange: true
31 | })
32 | }
33 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ng-reactive",
3 | "version": "0.0.0",
4 | "scripts": {
5 | "ng": "ng",
6 | "start": "ng serve",
7 | "build": "ng build",
8 | "test": "ng test",
9 | "lint": "ng lint",
10 | "e2e": "ng e2e"
11 | },
12 | "private": true,
13 | "dependencies": {
14 | "@angular/animations": "~8.1.1",
15 | "@angular/common": "~8.1.1",
16 | "@angular/compiler": "~8.1.1",
17 | "@angular/core": "~8.1.1",
18 | "@angular/forms": "~8.1.1",
19 | "@angular/platform-browser": "~8.1.1",
20 | "@angular/platform-browser-dynamic": "~8.1.1",
21 | "@angular/router": "~8.1.1",
22 | "rxjs": "~6.4.0",
23 | "tslib": "^1.9.0",
24 | "zone.js": "~0.9.1"
25 | },
26 | "devDependencies": {
27 | "@angular-devkit/build-angular": "~0.801.1",
28 | "@angular-devkit/build-ng-packagr": "~0.801.1",
29 | "@angular/cli": "~8.1.1",
30 | "@angular/compiler-cli": "~8.1.1",
31 | "@angular/language-service": "~8.1.1",
32 | "@types/node": "~8.9.4",
33 | "@types/jasmine": "~3.3.8",
34 | "@types/jasminewd2": "~2.0.3",
35 | "codelyzer": "^5.0.0",
36 | "jasmine-core": "~3.4.0",
37 | "jasmine-spec-reporter": "~4.2.1",
38 | "karma": "~4.1.0",
39 | "karma-chrome-launcher": "~2.2.0",
40 | "karma-coverage-istanbul-reporter": "~2.0.1",
41 | "karma-jasmine": "~2.0.1",
42 | "karma-jasmine-html-reporter": "^1.4.0",
43 | "ng-packagr": "^5.1.0",
44 | "protractor": "~5.4.0",
45 | "ts-node": "~7.0.0",
46 | "tsickle": "^0.35.0",
47 | "tslint": "~5.15.0",
48 | "typescript": "~3.4.3"
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/projects/ng-reactive/README.md:
--------------------------------------------------------------------------------
1 | # ng-reactive
2 |
3 | See GitHub repo page for more information.
4 |
--------------------------------------------------------------------------------
/projects/ng-reactive/karma.conf.js:
--------------------------------------------------------------------------------
1 | // Karma configuration file, see link for more information
2 | // https://karma-runner.github.io/1.0/config/configuration-file.html
3 |
4 | module.exports = function (config) {
5 | config.set({
6 | basePath: '',
7 | frameworks: ['jasmine', '@angular-devkit/build-angular'],
8 | plugins: [
9 | require('karma-jasmine'),
10 | require('karma-chrome-launcher'),
11 | require('karma-jasmine-html-reporter'),
12 | require('karma-coverage-istanbul-reporter'),
13 | require('@angular-devkit/build-angular/plugins/karma')
14 | ],
15 | client: {
16 | clearContext: false // leave Jasmine Spec Runner output visible in browser
17 | },
18 | coverageIstanbulReporter: {
19 | dir: require('path').join(__dirname, '../../coverage/ng-reactive'),
20 | reports: ['html', 'lcovonly', 'text-summary'],
21 | fixWebpackSourcePaths: true
22 | },
23 | reporters: ['progress', 'kjhtml'],
24 | port: 9876,
25 | colors: true,
26 | logLevel: config.LOG_INFO,
27 | autoWatch: true,
28 | browsers: ['Chrome'],
29 | singleRun: false,
30 | restartOnFileChange: true
31 | })
32 | }
33 |
--------------------------------------------------------------------------------
/projects/ng-reactive/ng-package.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "../../node_modules/ng-packagr/ng-package.schema.json",
3 | "dest": "../../dist/ng-reactive",
4 | "lib": {
5 | "entryFile": "src/public-api.ts"
6 | }
7 | }
--------------------------------------------------------------------------------
/projects/ng-reactive/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ng-reactive",
3 | "version": "0.0.1",
4 | "repository": {
5 | "type": "git",
6 | "url": "https://github.com/trotyl/ng-reactive.git"
7 | },
8 | "peerDependencies": {
9 | "@angular/core": "^8.0.0",
10 | "rxjs": "^6.0.0"
11 | }
12 | }
--------------------------------------------------------------------------------
/projects/ng-reactive/src/lib/metadata.ts:
--------------------------------------------------------------------------------
1 | import { Subscription } from 'rxjs'
2 |
3 | export const enum InstanceMetaKey {
4 | patched,
5 | hasPendingChange,
6 | properties,
7 | }
8 |
9 | export interface InstanceMeta extends Array {
10 | [InstanceMetaKey.patched]: boolean
11 | [InstanceMetaKey.hasPendingChange]: boolean
12 | [InstanceMetaKey.properties]: { [property: string]: InstancePropertyMeta }
13 | }
14 |
15 | export const enum ProtoMetaKey {
16 | patched,
17 | properties,
18 | }
19 |
20 | export interface ProtoMeta extends Array {
21 | [ProtoMetaKey.patched]: boolean
22 | [ProtoMetaKey.properties]: { [property: string]: ProtoPropertyMeta }
23 | }
24 |
25 | export const enum InstancePropertyMetaKey {
26 | defaultValue,
27 | currentValue,
28 | previousValue,
29 | hasPendingChange,
30 | changesCount,
31 | subscription,
32 | }
33 |
34 | export interface InstancePropertyMeta extends Array {
35 | [InstancePropertyMetaKey.defaultValue]: T
36 | [InstancePropertyMetaKey.currentValue]: T
37 | [InstancePropertyMetaKey.previousValue]: T | null
38 | [InstancePropertyMetaKey.hasPendingChange]: boolean
39 | [InstancePropertyMetaKey.changesCount]: number
40 | [InstancePropertyMetaKey.subscription]: Subscription | null
41 | }
42 |
43 | export const enum ProtoPropertyMetaKey {
44 | field = 0,
45 | }
46 |
47 | export interface ProtoPropertyMeta extends Array {
48 | [ProtoPropertyMetaKey.field]: Field
49 | }
50 |
51 | type Field = WeakMap