crudjt 1.0.0-beta.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/FUNDING.yml +15 -0
- package/.github/workflows/build.yml +42 -0
- package/CHANGELOG.md +12 -0
- package/Cache.js +82 -0
- package/Cargo.lock +176 -0
- package/Cargo.toml +16 -0
- package/LICENSE.txt +21 -0
- package/README.md +215 -0
- package/cargo.log +30 -0
- package/crudjt-1.0.0-beta.0.tgz +0 -0
- package/errors/internal_error.js +12 -0
- package/errors/invalid_state.js +12 -0
- package/errors.js +13 -0
- package/index.js +440 -0
- package/index.node +0 -0
- package/logos/buy_me_a_coffee_orange.svg +1 -0
- package/logos/crud_jt_logo.png +0 -0
- package/logos/crud_jt_logo_black.png +0 -0
- package/logos/crud_jt_logo_favicon_black.png +0 -0
- package/logos/crud_jt_logo_favicon_black_160.png +0 -0
- package/logos/crud_jt_logo_favicon_white.png +0 -0
- package/logos/crud_jt_logo_favicon_white_160.png +0 -0
- package/logos/crudjt_favicon_160x160_dark_on_white.svg +18 -0
- package/logos/crudjt_favicon_160x160_white.png +0 -0
- package/logos/crudjt_favicon_160x160_white_on_dark.svg +18 -0
- package/logos/crudjt_favicon_white_on_dark.png +0 -0
- package/logos/crudjt_logo_dark.png +0 -0
- package/logos/crudjt_logo_dark_on_white.svg +19 -0
- package/logos/crudjt_logo_white_on_dark.svg +26 -0
- package/native/linux/store_jt_arm64.so +0 -0
- package/native/linux/store_jt_x86_64.so +0 -0
- package/native/macos/store_jt_arm64.dylib +0 -0
- package/native/macos/store_jt_x86_64.dylib +0 -0
- package/native/windows/store_jt_arm64.dll +0 -0
- package/native/windows/store_jt_x86_64.dll +0 -0
- package/package/.github/workflows/build.yml +43 -0
- package/package/Cache.js +78 -0
- package/package/Cargo.lock +217 -0
- package/package/Cargo.toml +16 -0
- package/package/README.md +90 -0
- package/package/Validation.js +26 -0
- package/package/cargo.log +24 -0
- package/package/index.js +197 -0
- package/package/index.node +0 -0
- package/package/native/linux/store_jt_arm64.so +0 -0
- package/package/native/linux/store_jt_x86_64.so +0 -0
- package/package/native/macos/store_jt_arm64.dylib +0 -0
- package/package/native/macos/store_jt_x86_64.dylib +0 -0
- package/package/native/windows/store_jt_arm64.dll +0 -0
- package/package/native/windows/store_jt_x86_64.dll +0 -0
- package/package/package.json +26 -0
- package/package/src/lib.rs +190 -0
- package/package/test-neon-package/package-lock.json +942 -0
- package/package/test-neon-package/package.json +15 -0
- package/package/test-neon-package/test.js +178 -0
- package/package/test.js +202 -0
- package/package.json +41 -0
- package/src/lib.rs +288 -0
- package/test-neon-package/package-lock.json +1135 -0
- package/test-neon-package/package.json +15 -0
- package/test-neon-package/test.js +198 -0
- package/token_service.proto +47 -0
- package/token_service_client.js +33 -0
- package/validation.js +72 -0
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "test-neon-package",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"main": "index.js",
|
|
5
|
+
"scripts": {
|
|
6
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
|
7
|
+
},
|
|
8
|
+
"keywords": [],
|
|
9
|
+
"author": "",
|
|
10
|
+
"license": "ISC",
|
|
11
|
+
"description": "",
|
|
12
|
+
"dependencies": {
|
|
13
|
+
"crudjt": "file:../crudjt-1.0.0-beta.0.tgz"
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
// This binding was generated automatically to ensure consistency across languages
|
|
2
|
+
// Generated using ChatGPT (GPT-5) from the canonical Ruby SDK
|
|
3
|
+
// API is stable and production-ready
|
|
4
|
+
|
|
5
|
+
const CRUDJT = require('crudjt');
|
|
6
|
+
const { performance } = require('perf_hooks');
|
|
7
|
+
const os = require('os');
|
|
8
|
+
|
|
9
|
+
function sortObjectByKeyRecursive(obj) {
|
|
10
|
+
if (typeof obj !== 'object' || obj === null) return obj;
|
|
11
|
+
|
|
12
|
+
const sorted = Object.keys(obj).sort().reduce((result, key) => {
|
|
13
|
+
result[key] = sortObjectByKeyRecursive(obj[key]);
|
|
14
|
+
return result;
|
|
15
|
+
}, {});
|
|
16
|
+
|
|
17
|
+
return sorted;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function objectToString(obj) {
|
|
21
|
+
return JSON.stringify(sortObjectByKeyRecursive(obj));
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
if (process.env.CRUDJT_AUTOTEST_ALLOWED !== 'true') {
|
|
25
|
+
console.log("Denied run autotest for this environment. Set CRUDJT_AUTOTEST_ALLOWED='true'");
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
console.log(`OS: ${process.platform}`);
|
|
30
|
+
console.log(`CPU: ${os.arch()}`);
|
|
31
|
+
|
|
32
|
+
async function main() {
|
|
33
|
+
await CRUDJT.Config.startMaster({
|
|
34
|
+
secret_key: 'Cm7B68NWsMNNYjzMDREacmpe5sI1o0g40ZC9w1yQW3WOes7Gm59UsittLOHR2dciYiwmaYq98l3tG8h9yXVCxg=='
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
// without metadata
|
|
38
|
+
console.log('Checking without metadata...');
|
|
39
|
+
let data = { user_id: 42, role: 11 };
|
|
40
|
+
let expectedData = sortObjectByKeyRecursive({ data: { ...data } });
|
|
41
|
+
|
|
42
|
+
let edData = { user_id: 42, role: 8 };
|
|
43
|
+
let expectedEdData = sortObjectByKeyRecursive({ data: { ...edData } });
|
|
44
|
+
|
|
45
|
+
let token = await CRUDJT.create(data);
|
|
46
|
+
|
|
47
|
+
console.log(objectToString(await CRUDJT.read(token)) === objectToString(expectedData));
|
|
48
|
+
console.log(await CRUDJT.update(token, edData) === true);
|
|
49
|
+
console.log(objectToString(await CRUDJT.read(token)) === objectToString(expectedEdData));
|
|
50
|
+
console.log(await CRUDJT.delete(token) === true);
|
|
51
|
+
console.log(await CRUDJT.read(token) === null);
|
|
52
|
+
|
|
53
|
+
// with metadata
|
|
54
|
+
console.log('Checking ttl...');
|
|
55
|
+
data = { user_id: 42, role: 11 };
|
|
56
|
+
|
|
57
|
+
let ttl = 5;
|
|
58
|
+
let tokenWithttl = await CRUDJT.create(data, ttl);
|
|
59
|
+
|
|
60
|
+
let expectedttl = ttl;
|
|
61
|
+
for (let i = 0; i < ttl; i++) {
|
|
62
|
+
console.log(objectToString(await CRUDJT.read(tokenWithttl)) === objectToString({ metadata: { ttl: expectedttl }, data: data }));
|
|
63
|
+
expectedttl -= 1;
|
|
64
|
+
|
|
65
|
+
await new Promise(resolve => setTimeout(resolve, 1000)); // Затримка 1 секунда
|
|
66
|
+
}
|
|
67
|
+
console.log(await CRUDJT.read(tokenWithttl) === null);
|
|
68
|
+
|
|
69
|
+
// when expired ttl
|
|
70
|
+
console.log('when expired ttl');
|
|
71
|
+
data = { user_id: 42, role: 11 };
|
|
72
|
+
ttl = 1;
|
|
73
|
+
token = await CRUDJT.create(data, ttl);
|
|
74
|
+
await new Promise(resolve => setTimeout(resolve, ttl * 1000)); // Затримка на ttl секунд
|
|
75
|
+
console.log(await CRUDJT.read(token) === null);
|
|
76
|
+
console.log(await CRUDJT.update(token, data) === false);
|
|
77
|
+
console.log(await CRUDJT.delete(token) === false);
|
|
78
|
+
|
|
79
|
+
console.log(await CRUDJT.update(token, data) === false);
|
|
80
|
+
console.log(await CRUDJT.read(token) === null);
|
|
81
|
+
|
|
82
|
+
// with silence_read
|
|
83
|
+
console.log("Checking silence_read...");
|
|
84
|
+
data = { user_id: 42, role: 11 };
|
|
85
|
+
let silence_read = 6;
|
|
86
|
+
let tokenWithsilence_read = await CRUDJT.create(data, null, silence_read);
|
|
87
|
+
|
|
88
|
+
let expectedsilence_read = silence_read - 1;
|
|
89
|
+
for (let i = 0; i < silence_read; i++) {
|
|
90
|
+
console.log(objectToString(await CRUDJT.read(tokenWithsilence_read)) === objectToString({ metadata: { silence_read: expectedsilence_read }, data: data }));
|
|
91
|
+
expectedsilence_read -= 1;
|
|
92
|
+
}
|
|
93
|
+
console.log(await CRUDJT.read(tokenWithsilence_read) === null);
|
|
94
|
+
|
|
95
|
+
// with ttl and silence_read
|
|
96
|
+
console.log("Checking ttl and silence_read...");
|
|
97
|
+
data = { user_id: 42, role: 11 };
|
|
98
|
+
ttl = 5;
|
|
99
|
+
silence_read = 5;
|
|
100
|
+
expectedttl = 5;
|
|
101
|
+
expectedsilence_read = silence_read - 1;
|
|
102
|
+
let tokenWithttlAndsilence_read = await CRUDJT.create(data, ttl, silence_read);
|
|
103
|
+
|
|
104
|
+
for (let i = 0; i < silence_read; i++) {
|
|
105
|
+
console.log(objectToString(await CRUDJT.read(tokenWithttlAndsilence_read)) === objectToString({ metadata: { ttl: expectedttl, silence_read: expectedsilence_read }, data: data }));
|
|
106
|
+
expectedttl -= 1;
|
|
107
|
+
expectedsilence_read -= 1;
|
|
108
|
+
|
|
109
|
+
await new Promise(resolve => setTimeout(resolve, 1000)); // sleep 1 second
|
|
110
|
+
}
|
|
111
|
+
console.log(await CRUDJT.read(tokenWithttlAndsilence_read) === null);
|
|
112
|
+
|
|
113
|
+
// with scale load
|
|
114
|
+
const REQUESTS = 40_000;
|
|
115
|
+
|
|
116
|
+
for (let j = 0; j < 10; j++) {
|
|
117
|
+
let tokens = [];
|
|
118
|
+
data = {
|
|
119
|
+
user_id: 414243,
|
|
120
|
+
role: 11,
|
|
121
|
+
devices: {
|
|
122
|
+
ios_expired_at: "new Date().toString()",
|
|
123
|
+
android_expired_at: "new Date().toString()",
|
|
124
|
+
mobile_app_expired_at: "new Date().toString()",
|
|
125
|
+
external_api_integration_expired_at: "new Date().toString()",
|
|
126
|
+
},
|
|
127
|
+
a: 42
|
|
128
|
+
};
|
|
129
|
+
edData = { user_id: 42, role: 11 };
|
|
130
|
+
|
|
131
|
+
console.log('Checking scale load...');
|
|
132
|
+
|
|
133
|
+
console.log('when creates 40k tokens');
|
|
134
|
+
let start = performance.now();
|
|
135
|
+
for (let i = 0; i < REQUESTS; i++) {
|
|
136
|
+
tokens.push(await CRUDJT.create(data));
|
|
137
|
+
}
|
|
138
|
+
console.log(`Elapsed time: ${((performance.now() - start) / 1000).toFixed(3)}`);
|
|
139
|
+
|
|
140
|
+
console.log('when reads 40k tokens');
|
|
141
|
+
let index = Math.floor(Math.random() * REQUESTS);
|
|
142
|
+
start = performance.now();
|
|
143
|
+
for (let i = 0; i < REQUESTS; i++) {
|
|
144
|
+
await CRUDJT.read(tokens[index]);
|
|
145
|
+
}
|
|
146
|
+
console.log(`Elapsed time: ${((performance.now() - start) / 1000).toFixed(3)}`);
|
|
147
|
+
|
|
148
|
+
console.log('when updates 40k tokens');
|
|
149
|
+
start = performance.now();
|
|
150
|
+
for (let i = 0; i < REQUESTS; i++) {
|
|
151
|
+
await CRUDJT.update(tokens[i], edData);
|
|
152
|
+
}
|
|
153
|
+
console.log(`Elapsed time: ${((performance.now() - start) / 1000).toFixed(3)}`);
|
|
154
|
+
|
|
155
|
+
console.log('when deletes 40k tokens');
|
|
156
|
+
start = performance.now();
|
|
157
|
+
for (let i = 0; i < REQUESTS; i++) {
|
|
158
|
+
await CRUDJT.delete(tokens[i]);
|
|
159
|
+
}
|
|
160
|
+
console.log(`Elapsed time: ${((performance.now() - start) / 1000).toFixed(3)}`);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
console.log('when caches after read from file system');
|
|
164
|
+
|
|
165
|
+
const ttlGH = 2;
|
|
166
|
+
|
|
167
|
+
data = {
|
|
168
|
+
user_id: 414243,
|
|
169
|
+
role: 11,
|
|
170
|
+
devices: {
|
|
171
|
+
ios_expired_at: new Date().toString(),
|
|
172
|
+
android_expired_at: new Date().toString(),
|
|
173
|
+
mobile_app_expired_at: new Date().toString(),
|
|
174
|
+
external_api_integration_expired_at: new Date().toString(),
|
|
175
|
+
},
|
|
176
|
+
a: 42
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
let previoustokens = [];
|
|
180
|
+
|
|
181
|
+
for (let i = 0; i < REQUESTS; i++) {
|
|
182
|
+
previoustokens.push(await CRUDJT.create(data));
|
|
183
|
+
}
|
|
184
|
+
for (let i = 0; i < REQUESTS; i++) {
|
|
185
|
+
await CRUDJT.create(data);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
for (let i = 0; i < ttlGH; i++) {
|
|
189
|
+
let start = performance.now();
|
|
190
|
+
for (let j = 0; j < REQUESTS; j++) {
|
|
191
|
+
await CRUDJT.read(previoustokens[j]);
|
|
192
|
+
}
|
|
193
|
+
console.log(`Elapsed time: ${((performance.now() - start) / 1000).toFixed(3)}`);
|
|
194
|
+
}
|
|
195
|
+
await CRUDJT.Config.shutdownServer();
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
main().catch(console.error);
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
syntax = "proto3";
|
|
2
|
+
|
|
3
|
+
package token;
|
|
4
|
+
|
|
5
|
+
service TokenService {
|
|
6
|
+
rpc CreateToken(CreateTokenRequest) returns (CreateTokenResponse);
|
|
7
|
+
rpc ReadToken(ReadTokenRequest) returns (ReadTokenResponse);
|
|
8
|
+
rpc UpdateToken(UpdateTokenRequest) returns (UpdateTokenResponse);
|
|
9
|
+
rpc DeleteToken(DeleteTokenRequest) returns (DeleteTokenResponse);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
message CreateTokenRequest {
|
|
13
|
+
bytes packed_data = 1;
|
|
14
|
+
int64 ttl = 2;
|
|
15
|
+
int64 silence_read = 3;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
message CreateTokenResponse {
|
|
19
|
+
string token = 1;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
message ReadTokenRequest {
|
|
23
|
+
string token = 1;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
message ReadTokenResponse {
|
|
27
|
+
bytes packed_data = 1;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
message UpdateTokenRequest {
|
|
31
|
+
string token = 1;
|
|
32
|
+
bytes packed_data = 2;
|
|
33
|
+
int64 ttl = 3;
|
|
34
|
+
int64 silence_read = 4;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
message UpdateTokenResponse {
|
|
38
|
+
bool result = 1;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
message DeleteTokenRequest {
|
|
42
|
+
string token = 1;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
message DeleteTokenResponse {
|
|
46
|
+
bool result = 1;
|
|
47
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
// This binding was generated automatically to ensure consistency across languages
|
|
2
|
+
// Generated using ChatGPT (GPT-5) from the canonical Ruby SDK
|
|
3
|
+
// API is stable and production-ready
|
|
4
|
+
|
|
5
|
+
const grpc = require('@grpc/grpc-js');
|
|
6
|
+
const protoLoader = require('@grpc/proto-loader');
|
|
7
|
+
const path = require('path');
|
|
8
|
+
|
|
9
|
+
const PROTO_PATH = path.join(__dirname, 'token_service.proto');
|
|
10
|
+
|
|
11
|
+
const packageDefinition = protoLoader.loadSync(PROTO_PATH, {
|
|
12
|
+
keepCase: true,
|
|
13
|
+
longs: String,
|
|
14
|
+
enums: String,
|
|
15
|
+
defaults: true,
|
|
16
|
+
oneofs: true,
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
const proto = grpc.loadPackageDefinition(packageDefinition);
|
|
20
|
+
const tokenProto = proto.token;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Factory for create stub
|
|
24
|
+
* @param {string} address - "host:port"
|
|
25
|
+
*/
|
|
26
|
+
function createStub(address) {
|
|
27
|
+
return new tokenProto.TokenService(
|
|
28
|
+
address,
|
|
29
|
+
grpc.credentials.createInsecure()
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
module.exports = { createStub };
|
package/validation.js
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
// This binding was generated automatically to ensure consistency across languages
|
|
2
|
+
// Generated using ChatGPT (GPT-5) from the canonical Ruby SDK
|
|
3
|
+
// API is stable and production-ready
|
|
4
|
+
|
|
5
|
+
const U64_MAX = BigInt(2) ** BigInt(64) - BigInt(1);
|
|
6
|
+
|
|
7
|
+
const MAX_HASH_SIZE = 256;
|
|
8
|
+
|
|
9
|
+
const ERROR_ALREADY_STARTED = 0;
|
|
10
|
+
const ERROR_NOT_STARTED = 1;
|
|
11
|
+
const ERROR_SECRET_KEY_NOT_SET = 2;
|
|
12
|
+
|
|
13
|
+
const ERROR_MESSAGES = {
|
|
14
|
+
[ERROR_ALREADY_STARTED]: 'CRUDJT already started',
|
|
15
|
+
[ERROR_NOT_STARTED]: 'CRUDJT has not started',
|
|
16
|
+
[ERROR_SECRET_KEY_NOT_SET]: 'Secret key is blank'
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class Validation {
|
|
21
|
+
static errorMessage(code) {
|
|
22
|
+
return Validation.ERROR_MESSAGES[code] || `Unknown error (${code})`;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
static validateInsertion(hash, ttl, silence_read) {
|
|
26
|
+
if (typeof hash !== 'object' || hash === null || Array.isArray(hash)) {
|
|
27
|
+
throw new Error("Must be a Hash (Object)");
|
|
28
|
+
}
|
|
29
|
+
if (ttl != -1 && !(BigInt(ttl) > 0 && BigInt(ttl) <= U64_MAX)) {
|
|
30
|
+
throw new Error("ttl should be greater than 0 and less than 2^64");
|
|
31
|
+
}
|
|
32
|
+
if (silence_read !== -1 && !(BigInt(silence_read) > 0 && BigInt(silence_read) <= U64_MAX)) {
|
|
33
|
+
throw new Error("silence_read should be greater than 0 and less than 2^64");
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
static validateToken(token) {
|
|
38
|
+
if (typeof token !== 'string') {
|
|
39
|
+
throw new Error("token must be a String");
|
|
40
|
+
}
|
|
41
|
+
if (token.length < 1) {
|
|
42
|
+
throw new Error("token can't be blank");
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
static validateHashBytesize(hashBytesize) {
|
|
47
|
+
if (hashBytesize > Validation.MAX_HASH_SIZE) {
|
|
48
|
+
throw new Error(`Hash can not be bigger than ${Validation.MAX_HASH_SIZE} bytesize`);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
static validateSecretKey(key) {
|
|
53
|
+
let decoded;
|
|
54
|
+
try {
|
|
55
|
+
decoded = Buffer.from(key, 'base64');
|
|
56
|
+
|
|
57
|
+
if (Buffer.from(decoded.toString('base64'), 'base64').length !== decoded.length) {
|
|
58
|
+
throw new Error();
|
|
59
|
+
}
|
|
60
|
+
} catch {
|
|
61
|
+
throw new TypeError(`'secret_key' must be a valid Base64 string`);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (![32, 48, 64].includes(decoded.length)) {
|
|
65
|
+
throw new TypeError(`'secret_key' must be exactly 32, 48, or 64 bytes. Got ${decoded.length} bytes`);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return true;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
module.exports = Validation;
|