@cocreate/usage 1.2.1 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/automated.yml +25 -38
- package/CHANGELOG.md +27 -0
- package/package.json +3 -11
- package/release.config.js +12 -4
- package/src/index.js +258 -178
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
name: Automated Workflow
|
|
2
|
+
|
|
2
3
|
on:
|
|
3
4
|
push:
|
|
4
5
|
branches:
|
|
5
6
|
- master
|
|
7
|
+
|
|
6
8
|
jobs:
|
|
7
9
|
about:
|
|
8
10
|
runs-on: ubuntu-latest
|
|
@@ -18,52 +20,37 @@ jobs:
|
|
|
18
20
|
with:
|
|
19
21
|
direction: overwrite-github
|
|
20
22
|
githubToken: "${{ secrets.GITHUB }}"
|
|
23
|
+
|
|
21
24
|
release:
|
|
22
25
|
runs-on: ubuntu-latest
|
|
23
26
|
steps:
|
|
24
27
|
- name: Checkout
|
|
25
|
-
uses: actions/checkout@
|
|
26
|
-
- name: Setup Node.js
|
|
27
|
-
uses: actions/setup-node@v3
|
|
28
|
+
uses: actions/checkout@v4
|
|
28
29
|
with:
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
30
|
+
fetch-depth: 0 # Required so semantic-release can trace git tags/history
|
|
31
|
+
|
|
32
|
+
- name: Setup Node.js
|
|
33
|
+
uses: actions/setup-node@v4
|
|
33
34
|
with:
|
|
34
|
-
|
|
35
|
-
|
|
35
|
+
node-version: 22
|
|
36
|
+
|
|
37
|
+
- name: Install Semantic Release & Plugins
|
|
38
|
+
# Installs semantic-release and its plugins on the runner
|
|
39
|
+
run: |
|
|
40
|
+
npm install -g semantic-release \
|
|
41
|
+
@semantic-release/changelog \
|
|
42
|
+
@semantic-release/npm \
|
|
43
|
+
@semantic-release/github \
|
|
36
44
|
@semantic-release/git
|
|
37
|
-
|
|
45
|
+
|
|
46
|
+
- name: Run Semantic Release (Native)
|
|
47
|
+
id: semantic
|
|
48
|
+
# This will automatically pick up your export default config file in the repository root
|
|
49
|
+
run: npx semantic-release
|
|
38
50
|
env:
|
|
39
|
-
GITHUB_TOKEN: "${{ secrets.
|
|
51
|
+
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
|
|
40
52
|
NPM_TOKEN: "${{ secrets.NPM_TOKEN }}"
|
|
53
|
+
|
|
41
54
|
outputs:
|
|
42
55
|
new_release_published: "${{ steps.semantic.outputs.new_release_published }}"
|
|
43
|
-
new_release_version: "${{ steps.semantic.outputs.new_release_version }}"
|
|
44
|
-
upload:
|
|
45
|
-
runs-on: ubuntu-latest
|
|
46
|
-
needs: release
|
|
47
|
-
if: needs.release.outputs.new_release_published == 'true'
|
|
48
|
-
env:
|
|
49
|
-
VERSION: "${{ needs.release.outputs.new_release_version }}"
|
|
50
|
-
steps:
|
|
51
|
-
- name: Checkout
|
|
52
|
-
uses: actions/checkout@v3
|
|
53
|
-
- name: Setup Node.js
|
|
54
|
-
uses: actions/setup-node@v3
|
|
55
|
-
with:
|
|
56
|
-
node-version: 16
|
|
57
|
-
- name: Set npm registry auth
|
|
58
|
-
run: echo "//registry.npmjs.org/:_authToken=${{ secrets.NPM_TOKEN }}" > ~/.npmrc
|
|
59
|
-
- name: Install dependencies
|
|
60
|
-
run: yarn install
|
|
61
|
-
|
|
62
|
-
- name: Set Environment Variables
|
|
63
|
-
run: |
|
|
64
|
-
echo "organization_id=${{ secrets.COCREATE_ORGANIZATION_ID }}" >> $GITHUB_ENV
|
|
65
|
-
echo "key=${{ secrets.COCREATE_KEY }}" >> $GITHUB_ENV
|
|
66
|
-
echo "host=${{ secrets.COCREATE_HOST }}" >> $GITHUB_ENV
|
|
67
|
-
- name: CoCreate Upload
|
|
68
|
-
run: coc upload
|
|
69
|
-
|
|
56
|
+
new_release_version: "${{ steps.semantic.outputs.new_release_version }}"
|
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,30 @@
|
|
|
1
|
+
# [1.4.0](https://github.com/CoCreate-app/CoCreate-usage/compare/v1.3.0...v1.4.0) (2026-07-18)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Features
|
|
5
|
+
|
|
6
|
+
* enhance event listener handling and improve logging for organization deletion ([cca2e40](https://github.com/CoCreate-app/CoCreate-usage/commit/cca2e408ba2c6599aab2b56430b1d74b923fbb57))
|
|
7
|
+
* refactor usage tracking with new bandwidth management and event listeners ([76f8ddb](https://github.com/CoCreate-app/CoCreate-usage/commit/76f8ddb539ad6a3384ed5d1557b1e36b9d64222a))
|
|
8
|
+
|
|
9
|
+
# [1.3.0](https://github.com/CoCreate-app/CoCreate-usage/compare/v1.2.2...v1.3.0) (2026-07-17)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
### Features
|
|
13
|
+
|
|
14
|
+
* update automated workflow and release configuration for improved semantic release handling ([47aa8c2](https://github.com/CoCreate-app/CoCreate-usage/commit/47aa8c2941f8393a0ca55cbf6f66ae4df1172dea))
|
|
15
|
+
|
|
16
|
+
## [1.2.2](https://github.com/CoCreate-app/CoCreate-usage/compare/v1.2.1...v1.2.2) (2026-07-14)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
### Bug Fixes
|
|
20
|
+
|
|
21
|
+
* remove unnecessary console logs in init function for cleaner output ([af5f039](https://github.com/CoCreate-app/CoCreate-usage/commit/af5f03958fa5e84008411e99186bcdea438e7087))
|
|
22
|
+
* removed post install ([933f2b6](https://github.com/CoCreate-app/CoCreate-usage/commit/933f2b6b9a7a551afb7e2723bfd02de9a72f2019))
|
|
23
|
+
* semantic version handling. Reorganize .gitignore for improved clarity and structure ([9d67031](https://github.com/CoCreate-app/CoCreate-usage/commit/9d67031f6144a5b767ff0a2b6d487b14cbbd33aa))
|
|
24
|
+
* update module export to ES6 syntax in release.config.js ([8908cb7](https://github.com/CoCreate-app/CoCreate-usage/commit/8908cb7ea61ceb65d1151909e8706c0a54046903))
|
|
25
|
+
* update organization ID reference and host variable in sendUsageUpdate function ([4f4cc11](https://github.com/CoCreate-app/CoCreate-usage/commit/4f4cc11f9d0757b27c71f180ce83895fb2c436cd))
|
|
26
|
+
* update package.json to use ESM and restructure entry points ([c96c5db](https://github.com/CoCreate-app/CoCreate-usage/commit/c96c5db22a621d2af2a151d2173ffaa265c03f6e))
|
|
27
|
+
|
|
1
28
|
## [1.2.1](https://github.com/CoCreate-app/CoCreate-usage/compare/v1.2.0...v1.2.1) (2025-05-01)
|
|
2
29
|
|
|
3
30
|
|
package/package.json
CHANGED
|
@@ -1,18 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cocreate/usage",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.0",
|
|
4
4
|
"description": "CoCreate-usage",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"cocreate-usage",
|
|
7
|
-
"cocreate",
|
|
8
|
-
"low-code-framework",
|
|
9
|
-
"no-code-framework",
|
|
10
|
-
"cocreatejs",
|
|
11
|
-
"cocreatejs-component",
|
|
12
|
-
"cocreate-framework",
|
|
13
|
-
"no-code",
|
|
14
7
|
"low-code",
|
|
15
|
-
"collaborative-framework",
|
|
16
8
|
"realtime",
|
|
17
9
|
"realtime-framework",
|
|
18
10
|
"collaboration",
|
|
@@ -20,6 +12,7 @@
|
|
|
20
12
|
"html5-framework",
|
|
21
13
|
"javascript-framework"
|
|
22
14
|
],
|
|
15
|
+
"type": "module",
|
|
23
16
|
"main": "./src/index.js",
|
|
24
17
|
"publishConfig": {
|
|
25
18
|
"access": "public"
|
|
@@ -36,7 +29,6 @@
|
|
|
36
29
|
"homepage": "https://cocreate.app/docs/CoCreate-usage",
|
|
37
30
|
"scripts": {
|
|
38
31
|
"demo": "PORT=5000 node demo/server.js",
|
|
39
|
-
"test": "echo \"Error: no test specified\" && exit 1"
|
|
40
|
-
"postinstall": "node -e \"const { execSync } = require('child_process'); try { execSync('coc --version', { stdio: 'ignore' }); } catch (error) { try { execSync('npm install -g @cocreate/cli', { stdio: 'inherit' }); console.log('Installed \"@cocreate/cli\" globally.'); } catch (error) { console.error('Failed to install \"@cocreate/cli\" globally:', error); } }\""
|
|
32
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
|
41
33
|
}
|
|
42
34
|
}
|
package/release.config.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
export default {
|
|
2
2
|
dryRun: false,
|
|
3
3
|
branches: ["master"],
|
|
4
4
|
plugins: [
|
|
@@ -10,12 +10,20 @@ module.exports = {
|
|
|
10
10
|
changelogFile: "CHANGELOG.md",
|
|
11
11
|
},
|
|
12
12
|
],
|
|
13
|
-
"@semantic-release/npm",
|
|
14
|
-
"@semantic-release/github",
|
|
13
|
+
"@semantic-release/npm",
|
|
15
14
|
[
|
|
16
|
-
"@semantic-release/
|
|
15
|
+
"@semantic-release/github",
|
|
17
16
|
{
|
|
17
|
+
successComment: false,
|
|
18
|
+
failTitle: false,
|
|
19
|
+
},
|
|
20
|
+
],
|
|
21
|
+
[
|
|
22
|
+
"@semantic-release/git",
|
|
23
|
+
{
|
|
24
|
+
// Only stage and commit the changelog and package.json
|
|
18
25
|
assets: ["CHANGELOG.md", "package.json"],
|
|
26
|
+
message: "chore(release): ${nextRelease.version} [skip ci]",
|
|
19
27
|
},
|
|
20
28
|
],
|
|
21
29
|
],
|
package/src/index.js
CHANGED
|
@@ -1,208 +1,288 @@
|
|
|
1
|
+
/********************************************************************************
|
|
2
|
+
* Copyright (C) 2023 CoCreate and Contributors.
|
|
3
|
+
*
|
|
4
|
+
* This program is free software: you can redistribute it and/or modify
|
|
5
|
+
* it under the terms of the GNU Affero General Public License as published
|
|
6
|
+
* by the Free Software Foundation, either version 3 of the License, or
|
|
7
|
+
* (at your option) any later version.
|
|
8
|
+
*
|
|
9
|
+
* This program is distributed in the hope that it will be useful,
|
|
10
|
+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
11
|
+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
12
|
+
* GNU Affero General Public License for more details.
|
|
13
|
+
*
|
|
14
|
+
* You should have received a copy of the GNU Affero General Public License
|
|
15
|
+
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
16
|
+
********************************************************************************/
|
|
17
|
+
|
|
18
|
+
import crud from '@cocreate/crud-server';
|
|
19
|
+
|
|
1
20
|
class CoCreateUsage {
|
|
2
|
-
constructor(
|
|
3
|
-
this.
|
|
4
|
-
this.crud = crud;
|
|
5
|
-
this.init();
|
|
6
|
-
this.organizations = new Map()
|
|
7
|
-
}
|
|
21
|
+
constructor(platformOrgId) {
|
|
22
|
+
this.platformOrgId = platformOrgId; // Master platform organization ID
|
|
8
23
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
24
|
+
// Constants
|
|
25
|
+
this.FLUSH_THRESHOLD_BYTES = 10 * 1024 * 1024; // 10 Megabytes
|
|
26
|
+
this.TENANT_INTERVAL_MS = 60 * 1000; // 1 Minute
|
|
27
|
+
|
|
28
|
+
// Double-buffer map tracking separate platform and tenant blocks per organization
|
|
29
|
+
this.bandwidthMap = new Map();
|
|
30
|
+
|
|
31
|
+
// Initialize the Event Listeners
|
|
32
|
+
this.initEventListeners();
|
|
13
33
|
}
|
|
14
34
|
|
|
35
|
+
/**
|
|
36
|
+
* Connects directly to process event signals.
|
|
37
|
+
*/
|
|
38
|
+
initEventListeners() {
|
|
39
|
+
// Unified Application-Wide System Interceptor
|
|
40
|
+
// Expects an input object: { organization_id, data, type: 'ingress' | 'egress' }
|
|
41
|
+
process.on('usage', (payload) => {
|
|
42
|
+
if (!payload || typeof payload !== 'object') return;
|
|
43
|
+
|
|
44
|
+
const { organization_id, data, type } = payload;
|
|
45
|
+
if (organization_id) {
|
|
46
|
+
this.trackUsage(organization_id, data, type || 'ingress');
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
// Dynamic Eviction Loop: Caught via native process messaging.
|
|
51
|
+
// Triggers clean flushes and timer de-allocations during standard disconnects or orchestrator shutdowns.
|
|
52
|
+
process.on('orgDeleted', async (organization_id) => {
|
|
53
|
+
if (this.bandwidthMap.has(organization_id)) {
|
|
54
|
+
console.log(`[USAGE] Event Received: Flushing pending data pipelines for Org: ${organization_id}`);
|
|
55
|
+
try {
|
|
56
|
+
await this.flushAndClearSingle(organization_id);
|
|
57
|
+
console.log(`[USAGE] Flushing Completed: Memory registry successfully cleared for Org: ${organization_id}`);
|
|
58
|
+
} catch (err) {
|
|
59
|
+
console.error(`[USAGE ERROR] Failed to cleanly flush data during orgDeleted event for ${organization_id}:`, err);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
}
|
|
15
64
|
|
|
16
|
-
|
|
65
|
+
/**
|
|
66
|
+
* High-performance, robust byte-calculation routine.
|
|
67
|
+
* Accurately parses binary files (images, video chunks), text responses, and data objects.
|
|
68
|
+
*/
|
|
69
|
+
calculatePayloadBytes(data) {
|
|
70
|
+
if (data === null || data === undefined) return 0;
|
|
71
|
+
|
|
17
72
|
try {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
return
|
|
22
|
-
// TODO: set duration
|
|
23
|
-
let org = this.organizations.get(organization_id)
|
|
24
|
-
if (!org) {
|
|
25
|
-
let self = this
|
|
26
|
-
|
|
27
|
-
org = {}
|
|
28
|
-
org.debounce = setTimeout(() => {
|
|
29
|
-
self.send(org, organization_id, data.host); // Call the callback to display the count
|
|
30
|
-
self.organizations.delete(organization_id)
|
|
31
|
-
}, 60000);
|
|
32
|
-
|
|
33
|
-
org.dataTransferedIn = 0;
|
|
34
|
-
org.dataTransferedInCount = 0;
|
|
35
|
-
org.dataTransferedOut = 0;
|
|
36
|
-
org.dataTransferedOutCount = 0;
|
|
37
|
-
|
|
38
|
-
this.organizations.set(organization_id, org)
|
|
73
|
+
// Case 1: Raw media streams, files, or binary chunks (Buffers / TypedArrays)
|
|
74
|
+
if (Buffer.isBuffer(data)) {
|
|
75
|
+
return data.length;
|
|
39
76
|
}
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
org.dataTransferedIn = dataTransfered;
|
|
43
|
-
org.dataTransferedInCount++
|
|
44
|
-
} else if (type === 'out') {
|
|
45
|
-
org.dataTransferedOut = dataTransfered;
|
|
46
|
-
org.dataTransferedOutCount++
|
|
47
|
-
} else {
|
|
48
|
-
console.log('else')
|
|
77
|
+
if (data instanceof Uint8Array || data.buffer instanceof ArrayBuffer) {
|
|
78
|
+
return data.byteLength || data.length || 0;
|
|
49
79
|
}
|
|
50
|
-
|
|
80
|
+
|
|
81
|
+
// Case 2: Plain Text / Base64 Encoded Strings
|
|
82
|
+
if (typeof data === 'string') {
|
|
83
|
+
return Buffer.byteLength(data, 'utf8');
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Case 3: Native JavaScript Objects & Arrays (Database schemas, API JSON packets)
|
|
87
|
+
if (typeof data === 'object') {
|
|
88
|
+
return Buffer.byteLength(JSON.stringify(data), 'utf8');
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Case 4: Fallback for primitive datatypes (numbers, booleans, etc.)
|
|
92
|
+
return Buffer.byteLength(String(data), 'utf8');
|
|
51
93
|
} catch (error) {
|
|
52
|
-
console.
|
|
94
|
+
console.error('[USAGE ERROR] Failed to safely calculate payload byte scale:', error);
|
|
95
|
+
return 0;
|
|
53
96
|
}
|
|
54
97
|
}
|
|
55
98
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
organization_id
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
}
|
|
99
|
+
/**
|
|
100
|
+
* Main entry point to track incoming traffic.
|
|
101
|
+
*/
|
|
102
|
+
trackUsage(organization_id, data, direction = 'ingress') {
|
|
103
|
+
const now = Date.now();
|
|
104
|
+
const incomingBytes = this.calculatePayloadBytes(data);
|
|
105
|
+
|
|
106
|
+
// If this is a brand new organization session, initialize our distinct tracking layers
|
|
107
|
+
if (!this.bandwidthMap.has(organization_id)) {
|
|
108
|
+
const tenantTimer = setInterval(() => {
|
|
109
|
+
this.flushTenantWindow(organization_id);
|
|
110
|
+
}, this.TENANT_INTERVAL_MS);
|
|
111
|
+
|
|
112
|
+
this.bandwidthMap.set(organization_id, {
|
|
113
|
+
// Buffer A: Tenant 1-minute accumulator (system_usage)
|
|
114
|
+
tenant: {
|
|
115
|
+
ingressBytes: 0,
|
|
116
|
+
egressBytes: 0,
|
|
117
|
+
totalBytes: 0,
|
|
118
|
+
count: 0,
|
|
119
|
+
sampleStart: now,
|
|
120
|
+
sampleEnd: now
|
|
121
|
+
},
|
|
122
|
+
// Buffer B: Platform master accumulator (system_bandwidth)
|
|
123
|
+
platform: {
|
|
124
|
+
ingressBytes: 0,
|
|
125
|
+
egressBytes: 0,
|
|
126
|
+
totalBytes: 0,
|
|
127
|
+
count: 0,
|
|
128
|
+
sampleStart: now,
|
|
129
|
+
sampleEnd: now
|
|
130
|
+
},
|
|
131
|
+
tenantTimer
|
|
132
|
+
});
|
|
91
133
|
}
|
|
92
134
|
|
|
93
|
-
|
|
94
|
-
let isExpired = false
|
|
95
|
-
if (organization.lastDeposit) {
|
|
96
|
-
let lastDeposit = new Date(organization.lastDeposit)
|
|
97
|
-
isExpired = lastDeposit <= timeStamp.setFullYear(timeStamp.getFullYear() - 1)
|
|
98
|
-
}
|
|
135
|
+
const stats = this.bandwidthMap.get(organization_id);
|
|
99
136
|
|
|
100
|
-
|
|
101
|
-
if (
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
this.crud.send({
|
|
106
|
-
method: 'object.create',
|
|
107
|
-
host: this.crud.config.host,
|
|
108
|
-
array: 'transactions',
|
|
109
|
-
object: {
|
|
110
|
-
organization_id,
|
|
111
|
-
type: "withdrawal", // deposit, credit, withdrawal, debit
|
|
112
|
-
dataTransfered: organization.dataTransfered,
|
|
113
|
-
previousTimeStamp
|
|
114
|
-
},
|
|
115
|
-
organization_id: platformOrganization,
|
|
116
|
-
timeStamp
|
|
117
|
-
});
|
|
118
|
-
organization.dataTransfered = 0
|
|
119
|
-
}
|
|
137
|
+
// 1. Accumulate Tenant Buffer (Flushes on interval)
|
|
138
|
+
if (direction === 'ingress') {
|
|
139
|
+
stats.tenant.ingressBytes += incomingBytes;
|
|
140
|
+
} else if (direction === 'egress') {
|
|
141
|
+
stats.tenant.egressBytes += incomingBytes;
|
|
120
142
|
}
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
amount = amount.toFixed(32)
|
|
131
|
-
amount = parseFloat(amount)
|
|
132
|
-
|
|
133
|
-
let balanceUpdate = {
|
|
134
|
-
method: 'object.update',
|
|
135
|
-
host: this.crud.config.host,
|
|
136
|
-
array: 'organizations',
|
|
137
|
-
object: { _id: organization_id },
|
|
138
|
-
organization_id: platformOrganization,
|
|
139
|
-
timeStamp
|
|
143
|
+
stats.tenant.totalBytes += incomingBytes;
|
|
144
|
+
stats.tenant.count += 1;
|
|
145
|
+
stats.tenant.sampleEnd = now;
|
|
146
|
+
|
|
147
|
+
// 2. Accumulate Platform Buffer (Flushes on event triggers / 10MB only)
|
|
148
|
+
if (direction === 'ingress') {
|
|
149
|
+
stats.platform.ingressBytes += incomingBytes;
|
|
150
|
+
} else if (direction === 'egress') {
|
|
151
|
+
stats.platform.egressBytes += incomingBytes;
|
|
140
152
|
}
|
|
153
|
+
stats.platform.totalBytes += incomingBytes;
|
|
154
|
+
stats.platform.count += 1;
|
|
155
|
+
stats.platform.sampleEnd = now;
|
|
141
156
|
|
|
142
|
-
if
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
balanceUpdate.object.$inc = { balance: amount }
|
|
146
|
-
|
|
147
|
-
if (isResetDataTransfer)
|
|
148
|
-
balanceUpdate.object['dataTransfered'] = 0
|
|
149
|
-
else if (!balanceUpdate.object.$inc)
|
|
150
|
-
balanceUpdate.object.$inc = { dataTransfered }
|
|
151
|
-
else
|
|
152
|
-
balanceUpdate.object.$inc.dataTransfered = dataTransfered
|
|
153
|
-
|
|
154
|
-
// console.log('balanceUpdate: ', balanceUpdate)
|
|
155
|
-
|
|
156
|
-
this.crud.send(balanceUpdate)
|
|
157
|
-
|
|
158
|
-
let transaction = {
|
|
159
|
-
method: 'object.create',
|
|
160
|
-
host,
|
|
161
|
-
array: 'transactions',
|
|
162
|
-
object: {
|
|
163
|
-
organization_id,
|
|
164
|
-
type: "withdrawal", // deposit, credit, withdrawal, debit
|
|
165
|
-
amount,
|
|
166
|
-
rate,
|
|
167
|
-
dataTransfered,
|
|
168
|
-
...org
|
|
169
|
-
},
|
|
170
|
-
organization_id,
|
|
171
|
-
timeStamp
|
|
157
|
+
// 3. Platform Trigger: Flush to the master platform ledger if we hit 10MB
|
|
158
|
+
if (stats.platform.totalBytes >= this.FLUSH_THRESHOLD_BYTES) {
|
|
159
|
+
this.flushPlatformWindow(organization_id);
|
|
172
160
|
}
|
|
161
|
+
}
|
|
173
162
|
|
|
174
|
-
|
|
175
|
-
|
|
163
|
+
/**
|
|
164
|
+
* PIPELINE A: Handles the 1-minute interval flush to the client tenant's database
|
|
165
|
+
*/
|
|
166
|
+
async flushTenantWindow(organization_id) {
|
|
167
|
+
const stats = this.bandwidthMap.get(organization_id);
|
|
168
|
+
if (!stats || stats.tenant.totalBytes === 0) return;
|
|
169
|
+
|
|
170
|
+
const tenantData = stats.tenant;
|
|
171
|
+
|
|
172
|
+
// Save exactly one document representing this complete 1-minute segment to their partition
|
|
173
|
+
await this.saveToTenantBilling(organization_id, tenantData);
|
|
174
|
+
|
|
175
|
+
// Reset ONLY the tenant accumulator to begin a fresh 1-minute block
|
|
176
|
+
const now = Date.now();
|
|
177
|
+
stats.tenant = {
|
|
178
|
+
ingressBytes: 0,
|
|
179
|
+
egressBytes: 0,
|
|
180
|
+
totalBytes: 0,
|
|
181
|
+
count: 0,
|
|
182
|
+
sampleStart: now,
|
|
183
|
+
sampleEnd: now
|
|
184
|
+
};
|
|
176
185
|
}
|
|
177
186
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
187
|
+
/**
|
|
188
|
+
* PIPELINE B: Handles targeted platform flushes (when 10MB limit is reached)
|
|
189
|
+
*/
|
|
190
|
+
async flushPlatformWindow(organization_id) {
|
|
191
|
+
const stats = this.bandwidthMap.get(organization_id);
|
|
192
|
+
if (!stats || stats.platform.totalBytes === 0) return;
|
|
193
|
+
|
|
194
|
+
const platformData = stats.platform;
|
|
195
|
+
|
|
196
|
+
// Save platform ledger record
|
|
197
|
+
await this.saveToLedgerDatabase(organization_id, platformData);
|
|
198
|
+
|
|
199
|
+
// Reset ONLY the platform long-term memory accumulator
|
|
200
|
+
const now = Date.now();
|
|
201
|
+
stats.platform = {
|
|
202
|
+
ingressBytes: 0,
|
|
203
|
+
egressBytes: 0,
|
|
204
|
+
totalBytes: 0,
|
|
205
|
+
count: 0,
|
|
206
|
+
sampleStart: now,
|
|
207
|
+
sampleEnd: now
|
|
208
|
+
};
|
|
188
209
|
}
|
|
189
210
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
211
|
+
/**
|
|
212
|
+
* Target eviction: Clears timers and flushes all outstanding tenant & platform balances
|
|
213
|
+
*/
|
|
214
|
+
async flushAndClearSingle(organization_id) {
|
|
215
|
+
const stats = this.bandwidthMap.get(organization_id);
|
|
216
|
+
if (!stats) return;
|
|
217
|
+
|
|
218
|
+
// De-allocate the 1-minute background loop instantly
|
|
219
|
+
if (stats.tenantTimer) {
|
|
220
|
+
clearInterval(stats.tenantTimer);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// Flush any partial unsaved records remaining in either pipeline
|
|
224
|
+
await Promise.all([
|
|
225
|
+
this.flushTenantWindow(organization_id),
|
|
226
|
+
this.flushPlatformWindow(organization_id)
|
|
227
|
+
]);
|
|
200
228
|
|
|
201
|
-
|
|
229
|
+
// Evict from active tracker
|
|
230
|
+
this.bandwidthMap.delete(organization_id);
|
|
231
|
+
}
|
|
202
232
|
|
|
203
|
-
|
|
233
|
+
/**
|
|
234
|
+
* CRUD Adapter: Platform Bandwidth Collection (Private time-series log)
|
|
235
|
+
* Writes into the platform's sandbox segment, cataloging target clients in "organization".
|
|
236
|
+
*/
|
|
237
|
+
async saveToLedgerDatabase(organization_id, stats) {
|
|
238
|
+
try {
|
|
239
|
+
await crud.send({
|
|
240
|
+
method: 'object.create',
|
|
241
|
+
array: 'system_bandwidth',
|
|
242
|
+
object: {
|
|
243
|
+
organization_id: this.platformOrgId, // Platform workspace sandbox route
|
|
244
|
+
organization: organization_id, // The actual client tenant who generated the bandwidth
|
|
245
|
+
ingressBytes: stats.ingressBytes,
|
|
246
|
+
egressBytes: stats.egressBytes,
|
|
247
|
+
totalBytes: stats.totalBytes,
|
|
248
|
+
totalRequests: stats.count,
|
|
249
|
+
sampleStart: new Date(stats.sampleStart),
|
|
250
|
+
sampleEnd: new Date(stats.sampleEnd),
|
|
251
|
+
durationMs: stats.sampleEnd - stats.sampleStart,
|
|
252
|
+
processed: false // Ready for asynchronous billing calculations
|
|
253
|
+
},
|
|
254
|
+
organization_id: this.platformOrgId // Outermost routing targeting the Platform's DB partition
|
|
255
|
+
});
|
|
256
|
+
} catch (error) {
|
|
257
|
+
console.error(`[DB ERROR] Failed to write to system_bandwidth for ${organization_id}:`, error);
|
|
258
|
+
}
|
|
204
259
|
}
|
|
205
260
|
|
|
261
|
+
/**
|
|
262
|
+
* CRUD Adapter: Tenant Usage Collection (Their 1-Minute Dashboard Stream)
|
|
263
|
+
* Writes directly to the tenant's partition under the safe, system namespace "system_usage"
|
|
264
|
+
*/
|
|
265
|
+
async saveToTenantBilling(organization_id, stats) {
|
|
266
|
+
try {
|
|
267
|
+
await crud.send({
|
|
268
|
+
method: 'object.create',
|
|
269
|
+
array: 'system_usage',
|
|
270
|
+
object: {
|
|
271
|
+
organization_id, // Tenant's sandbox segment route
|
|
272
|
+
ingressBytes: stats.ingressBytes,
|
|
273
|
+
egressBytes: stats.egressBytes,
|
|
274
|
+
totalBytes: stats.totalBytes,
|
|
275
|
+
totalRequests: stats.count,
|
|
276
|
+
sampleStart: new Date(stats.sampleStart),
|
|
277
|
+
sampleEnd: new Date(stats.sampleEnd),
|
|
278
|
+
durationMs: stats.sampleEnd - stats.sampleStart
|
|
279
|
+
},
|
|
280
|
+
organization_id: organization_id // Outermost routing targeting the Client's DB partition
|
|
281
|
+
});
|
|
282
|
+
} catch (error) {
|
|
283
|
+
console.error(`[DB ERROR] Failed to write to system_usage for ${organization_id}:`, error);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
206
286
|
}
|
|
207
287
|
|
|
208
|
-
|
|
288
|
+
export default CoCreateUsage;
|