@gigzen/populace 0.1.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/LICENSE +661 -0
- package/README.md +258 -0
- package/adapters/buzzbuzz.mjs +247 -0
- package/adapters/contract.md +164 -0
- package/adapters/template-rest.mjs +192 -0
- package/adapters/template.mjs +80 -0
- package/examples/buzzbuzz/populace-report.html +245 -0
- package/examples/buzzbuzz/populace-report.json +280 -0
- package/examples/buzzbuzz/populace.config.mjs +51 -0
- package/examples/buzzbuzz/run-test.ps1 +61 -0
- package/examples/demo/adapters/demo.mjs +90 -0
- package/examples/demo/populace-report.html +230 -0
- package/examples/demo/populace-report.json +219 -0
- package/examples/demo/populace.config.mjs +22 -0
- package/examples/rest-api/README.md +85 -0
- package/examples/rest-api/adapter.mjs +166 -0
- package/examples/rest-api/populace.config.mjs +40 -0
- package/examples/rest-api/server.mjs +247 -0
- package/examples/token-expiry/expiry-demo.mjs +119 -0
- package/package.json +56 -0
- package/populace.config.example.mjs +65 -0
- package/src/cli.mjs +591 -0
- package/src/config.mjs +186 -0
- package/src/contract.mjs +130 -0
- package/src/diagnose.mjs +40 -0
- package/src/engine/agent.mjs +264 -0
- package/src/engine/geo.mjs +59 -0
- package/src/engine/index.mjs +4 -0
- package/src/engine/personas.mjs +115 -0
- package/src/engine/world.mjs +120 -0
- package/src/html-report.mjs +218 -0
- package/src/index.mjs +38 -0
- package/src/instrument.mjs +299 -0
- package/src/net.mjs +175 -0
- package/src/report.mjs +251 -0
- package/src/selftest.mjs +1369 -0
- package/src/smoke.mjs +274 -0
- package/src/version.mjs +24 -0
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
{
|
|
2
|
+
"populace": {
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"generatedAt": "2026-08-15T11:32:20.651Z"
|
|
5
|
+
},
|
|
6
|
+
"run": {
|
|
7
|
+
"app": "Buzz Buzz",
|
|
8
|
+
"adapter": "../../adapters/buzzbuzz.mjs",
|
|
9
|
+
"environment": "test",
|
|
10
|
+
"startedAt": "2026-08-15T11:18:42.515Z",
|
|
11
|
+
"durationMs": 818136,
|
|
12
|
+
"population": {
|
|
13
|
+
"agents": 10,
|
|
14
|
+
"cities": [
|
|
15
|
+
"manila",
|
|
16
|
+
"mumbai"
|
|
17
|
+
],
|
|
18
|
+
"tickSeconds": 5,
|
|
19
|
+
"minutes": 10
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
"verdict": {
|
|
23
|
+
"status": "clean",
|
|
24
|
+
"problems": [],
|
|
25
|
+
"apiFailures": 0,
|
|
26
|
+
"transportFailures": 0,
|
|
27
|
+
"failingMethods": []
|
|
28
|
+
},
|
|
29
|
+
"population": {
|
|
30
|
+
"requested": 10,
|
|
31
|
+
"signedIn": 10,
|
|
32
|
+
"signupFailures": []
|
|
33
|
+
},
|
|
34
|
+
"engineErrors": [],
|
|
35
|
+
"activity": {
|
|
36
|
+
"distanceKm": 36,
|
|
37
|
+
"posts": 139,
|
|
38
|
+
"likes": 296,
|
|
39
|
+
"comments": 123,
|
|
40
|
+
"messages": 110,
|
|
41
|
+
"groupJoins": 16
|
|
42
|
+
},
|
|
43
|
+
"api": {
|
|
44
|
+
"calls": 2030,
|
|
45
|
+
"failures": 0,
|
|
46
|
+
"apiFailures": 0,
|
|
47
|
+
"transportFailures": 0,
|
|
48
|
+
"retries": 0,
|
|
49
|
+
"failureRate": 0,
|
|
50
|
+
"apiFailureRate": 0,
|
|
51
|
+
"network": {
|
|
52
|
+
"retries": 0,
|
|
53
|
+
"transportFailures": 0,
|
|
54
|
+
"retryRate": 0,
|
|
55
|
+
"healthy": true,
|
|
56
|
+
"gaveUp": false,
|
|
57
|
+
"gaveUpAfter": null,
|
|
58
|
+
"outages": 0
|
|
59
|
+
},
|
|
60
|
+
"durationMs": 818132,
|
|
61
|
+
"methods": [
|
|
62
|
+
{
|
|
63
|
+
"method": "reportLocation",
|
|
64
|
+
"calls": 892,
|
|
65
|
+
"failures": 0,
|
|
66
|
+
"apiFailures": 0,
|
|
67
|
+
"transportFailures": 0,
|
|
68
|
+
"retries": 0,
|
|
69
|
+
"failureRate": 0,
|
|
70
|
+
"latencyMs": {
|
|
71
|
+
"p50": 711,
|
|
72
|
+
"p95": 1003,
|
|
73
|
+
"p99": 2371,
|
|
74
|
+
"max": 3178
|
|
75
|
+
},
|
|
76
|
+
"errors": []
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
"method": "recentPostsByOthers",
|
|
80
|
+
"calls": 298,
|
|
81
|
+
"failures": 0,
|
|
82
|
+
"apiFailures": 0,
|
|
83
|
+
"transportFailures": 0,
|
|
84
|
+
"retries": 0,
|
|
85
|
+
"failureRate": 0,
|
|
86
|
+
"latencyMs": {
|
|
87
|
+
"p50": 241,
|
|
88
|
+
"p95": 349,
|
|
89
|
+
"p99": 416,
|
|
90
|
+
"max": 622
|
|
91
|
+
},
|
|
92
|
+
"errors": []
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
"method": "like",
|
|
96
|
+
"calls": 296,
|
|
97
|
+
"failures": 0,
|
|
98
|
+
"apiFailures": 0,
|
|
99
|
+
"transportFailures": 0,
|
|
100
|
+
"retries": 0,
|
|
101
|
+
"failureRate": 0,
|
|
102
|
+
"latencyMs": {
|
|
103
|
+
"p50": 238,
|
|
104
|
+
"p95": 319,
|
|
105
|
+
"p99": 385,
|
|
106
|
+
"max": 760
|
|
107
|
+
},
|
|
108
|
+
"errors": []
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
"method": "post",
|
|
112
|
+
"calls": 139,
|
|
113
|
+
"failures": 0,
|
|
114
|
+
"apiFailures": 0,
|
|
115
|
+
"transportFailures": 0,
|
|
116
|
+
"retries": 0,
|
|
117
|
+
"failureRate": 0,
|
|
118
|
+
"latencyMs": {
|
|
119
|
+
"p50": 240,
|
|
120
|
+
"p95": 313,
|
|
121
|
+
"p99": 383,
|
|
122
|
+
"max": 614
|
|
123
|
+
},
|
|
124
|
+
"errors": []
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
"method": "comment",
|
|
128
|
+
"calls": 123,
|
|
129
|
+
"failures": 0,
|
|
130
|
+
"apiFailures": 0,
|
|
131
|
+
"transportFailures": 0,
|
|
132
|
+
"retries": 0,
|
|
133
|
+
"failureRate": 0,
|
|
134
|
+
"latencyMs": {
|
|
135
|
+
"p50": 243,
|
|
136
|
+
"p95": 326,
|
|
137
|
+
"p99": 387,
|
|
138
|
+
"max": 683
|
|
139
|
+
},
|
|
140
|
+
"errors": []
|
|
141
|
+
},
|
|
142
|
+
{
|
|
143
|
+
"method": "openConversation",
|
|
144
|
+
"calls": 110,
|
|
145
|
+
"failures": 0,
|
|
146
|
+
"apiFailures": 0,
|
|
147
|
+
"transportFailures": 0,
|
|
148
|
+
"retries": 0,
|
|
149
|
+
"failureRate": 0,
|
|
150
|
+
"latencyMs": {
|
|
151
|
+
"p50": 245,
|
|
152
|
+
"p95": 336,
|
|
153
|
+
"p99": 375,
|
|
154
|
+
"max": 626
|
|
155
|
+
},
|
|
156
|
+
"errors": []
|
|
157
|
+
},
|
|
158
|
+
{
|
|
159
|
+
"method": "sendMessage",
|
|
160
|
+
"calls": 110,
|
|
161
|
+
"failures": 0,
|
|
162
|
+
"apiFailures": 0,
|
|
163
|
+
"transportFailures": 0,
|
|
164
|
+
"retries": 0,
|
|
165
|
+
"failureRate": 0,
|
|
166
|
+
"latencyMs": {
|
|
167
|
+
"p50": 244,
|
|
168
|
+
"p95": 351,
|
|
169
|
+
"p99": 393,
|
|
170
|
+
"max": 780
|
|
171
|
+
},
|
|
172
|
+
"errors": []
|
|
173
|
+
},
|
|
174
|
+
{
|
|
175
|
+
"method": "listGroups",
|
|
176
|
+
"calls": 16,
|
|
177
|
+
"failures": 0,
|
|
178
|
+
"apiFailures": 0,
|
|
179
|
+
"transportFailures": 0,
|
|
180
|
+
"retries": 0,
|
|
181
|
+
"failureRate": 0,
|
|
182
|
+
"latencyMs": {
|
|
183
|
+
"p50": 250,
|
|
184
|
+
"p95": 312,
|
|
185
|
+
"p99": 312,
|
|
186
|
+
"max": 312
|
|
187
|
+
},
|
|
188
|
+
"errors": []
|
|
189
|
+
},
|
|
190
|
+
{
|
|
191
|
+
"method": "joinGroup",
|
|
192
|
+
"calls": 16,
|
|
193
|
+
"failures": 0,
|
|
194
|
+
"apiFailures": 0,
|
|
195
|
+
"transportFailures": 0,
|
|
196
|
+
"retries": 0,
|
|
197
|
+
"failureRate": 0,
|
|
198
|
+
"latencyMs": {
|
|
199
|
+
"p50": 235,
|
|
200
|
+
"p95": 311,
|
|
201
|
+
"p99": 311,
|
|
202
|
+
"max": 311
|
|
203
|
+
},
|
|
204
|
+
"errors": []
|
|
205
|
+
},
|
|
206
|
+
{
|
|
207
|
+
"method": "createUser",
|
|
208
|
+
"calls": 10,
|
|
209
|
+
"failures": 0,
|
|
210
|
+
"apiFailures": 0,
|
|
211
|
+
"transportFailures": 0,
|
|
212
|
+
"retries": 0,
|
|
213
|
+
"failureRate": 0,
|
|
214
|
+
"latencyMs": {
|
|
215
|
+
"p50": 601,
|
|
216
|
+
"p95": 834,
|
|
217
|
+
"p99": 834,
|
|
218
|
+
"max": 834
|
|
219
|
+
},
|
|
220
|
+
"errors": []
|
|
221
|
+
},
|
|
222
|
+
{
|
|
223
|
+
"method": "setProfile",
|
|
224
|
+
"calls": 10,
|
|
225
|
+
"failures": 0,
|
|
226
|
+
"apiFailures": 0,
|
|
227
|
+
"transportFailures": 0,
|
|
228
|
+
"retries": 0,
|
|
229
|
+
"failureRate": 0,
|
|
230
|
+
"latencyMs": {
|
|
231
|
+
"p50": 249,
|
|
232
|
+
"p95": 392,
|
|
233
|
+
"p99": 392,
|
|
234
|
+
"max": 392
|
|
235
|
+
},
|
|
236
|
+
"errors": []
|
|
237
|
+
},
|
|
238
|
+
{
|
|
239
|
+
"method": "deleteUser",
|
|
240
|
+
"calls": 10,
|
|
241
|
+
"failures": 0,
|
|
242
|
+
"apiFailures": 0,
|
|
243
|
+
"transportFailures": 0,
|
|
244
|
+
"retries": 0,
|
|
245
|
+
"failureRate": 0,
|
|
246
|
+
"latencyMs": {
|
|
247
|
+
"p50": 320,
|
|
248
|
+
"p95": 508,
|
|
249
|
+
"p99": 508,
|
|
250
|
+
"max": 508
|
|
251
|
+
},
|
|
252
|
+
"errors": []
|
|
253
|
+
}
|
|
254
|
+
]
|
|
255
|
+
},
|
|
256
|
+
"coverage": {
|
|
257
|
+
"label": "13/13",
|
|
258
|
+
"implemented": [
|
|
259
|
+
"createUser",
|
|
260
|
+
"setProfile",
|
|
261
|
+
"refreshSession",
|
|
262
|
+
"reportLocation",
|
|
263
|
+
"post",
|
|
264
|
+
"recentPostsByOthers",
|
|
265
|
+
"like",
|
|
266
|
+
"comment",
|
|
267
|
+
"openConversation",
|
|
268
|
+
"sendMessage",
|
|
269
|
+
"listGroups",
|
|
270
|
+
"joinGroup",
|
|
271
|
+
"deleteUser"
|
|
272
|
+
],
|
|
273
|
+
"notTested": []
|
|
274
|
+
},
|
|
275
|
+
"cleanup": {
|
|
276
|
+
"removed": 10,
|
|
277
|
+
"notDeleted": [],
|
|
278
|
+
"failed": []
|
|
279
|
+
}
|
|
280
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
// Populace's first customer: Buzz Buzz, a gig-driver tracking app.
|
|
2
|
+
//
|
|
3
|
+
// Run it — PowerShell (Windows):
|
|
4
|
+
// $env:BUZZBUZZ_TEST_URL="https://<your-TEST-project>.supabase.co"
|
|
5
|
+
// $env:BUZZBUZZ_TEST_KEY="<test project publishable key>"
|
|
6
|
+
// node src/cli.mjs run --config examples/buzzbuzz/populace.config.mjs
|
|
7
|
+
//
|
|
8
|
+
// Run it — bash/zsh (macOS, Linux):
|
|
9
|
+
// BUZZBUZZ_TEST_URL=... BUZZBUZZ_TEST_KEY=... \
|
|
10
|
+
// node src/cli.mjs run --config examples/buzzbuzz/populace.config.mjs
|
|
11
|
+
//
|
|
12
|
+
// The test project needs the same schema as production. Run these against it:
|
|
13
|
+
// schema.sql · social_features.sql · groups.sql · direct_messages.sql
|
|
14
|
+
// realtime.sql · fix_chat_rls.sql · user_content_control.sql
|
|
15
|
+
// work_apps_global.sql · privacy_lockdown.sql
|
|
16
|
+
// …and turn OFF Authentication → Providers → Email → "Confirm email",
|
|
17
|
+
// otherwise nobody can sign in after signing up.
|
|
18
|
+
|
|
19
|
+
export default {
|
|
20
|
+
app: "Buzz Buzz",
|
|
21
|
+
adapter: "../../adapters/buzzbuzz.mjs",
|
|
22
|
+
environment: "test",
|
|
23
|
+
|
|
24
|
+
target: {
|
|
25
|
+
url: process.env.BUZZBUZZ_TEST_URL,
|
|
26
|
+
key: process.env.BUZZBUZZ_TEST_KEY,
|
|
27
|
+
},
|
|
28
|
+
|
|
29
|
+
// Buzz Buzz's LIVE project. If BUZZBUZZ_TEST_URL is ever set to this by
|
|
30
|
+
// accident, Populace refuses to start. Real drivers must never see invented
|
|
31
|
+
// people on their map.
|
|
32
|
+
// Both of these are real. rqzuuvlougzhynckvqzd is the original project, which
|
|
33
|
+
// is still running; ypdaetbeexyepswyhbui became production on 2026-08-09.
|
|
34
|
+
// The clean run recorded against the latter happened while it was empty and
|
|
35
|
+
// had no real drivers on it — that window has closed, and it must not be
|
|
36
|
+
// simulated against again.
|
|
37
|
+
neverRunAgainst: [
|
|
38
|
+
"https://rqzuuvlougzhynckvqzd.supabase.co",
|
|
39
|
+
"https://ypdaetbeexyepswyhbui.supabase.co",
|
|
40
|
+
],
|
|
41
|
+
|
|
42
|
+
population: {
|
|
43
|
+
agents: 8,
|
|
44
|
+
cities: ["manila", "mumbai"],
|
|
45
|
+
minutes: 10,
|
|
46
|
+
tickSeconds: 5,
|
|
47
|
+
},
|
|
48
|
+
|
|
49
|
+
identity: { phonePrefix: "0900" },
|
|
50
|
+
report: { path: "populace-report.json" },
|
|
51
|
+
};
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# Run the Buzz Buzz simulation from Windows PowerShell.
|
|
2
|
+
#
|
|
3
|
+
# Exists because the bash one-liner does not work here: PowerShell 5.1 has no
|
|
4
|
+
# `&&` statement separator and no `VAR=value command` prefix, so pasting the
|
|
5
|
+
# Unix form fails with "The token '&&' is not a valid statement separator".
|
|
6
|
+
#
|
|
7
|
+
# .\run-test.ps1 # 6 drivers, 5 minutes
|
|
8
|
+
# .\run-test.ps1 -Agents 4 -Minutes 3
|
|
9
|
+
# .\run-test.ps1 -Clean # just remove any leftover accounts
|
|
10
|
+
#
|
|
11
|
+
# Run it from anywhere; it locates itself.
|
|
12
|
+
|
|
13
|
+
param(
|
|
14
|
+
[int]$Agents = 6,
|
|
15
|
+
[int]$Minutes = 5,
|
|
16
|
+
[switch]$Clean
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
$ErrorActionPreference = "Stop"
|
|
20
|
+
Set-Location $PSScriptRoot
|
|
21
|
+
|
|
22
|
+
# Deliberately no NODE_OPTIONS here. An earlier version set
|
|
23
|
+
# --dns-result-order=ipv6first to work around connection failures on a NAT64
|
|
24
|
+
# link. Measuring it afterwards showed 40/40 successful connections both with
|
|
25
|
+
# and without the flag — the failures were the link recovering on its own, not
|
|
26
|
+
# address-family ordering. The workaround was cargo cult, so it is gone.
|
|
27
|
+
|
|
28
|
+
# The isolated test project. Never a production host — populace.config.mjs
|
|
29
|
+
# lists the live ones in neverRunAgainst and refuses them before loading.
|
|
30
|
+
$env:BUZZBUZZ_TEST_URL = "https://jqepegeifmnfofeyebrz.supabase.co"
|
|
31
|
+
$env:BUZZBUZZ_TEST_KEY = "sb_publishable_ZkOhFsiQBrKMB5pc2Ub1Vw_V5oxsBE1"
|
|
32
|
+
|
|
33
|
+
$cli = Join-Path $PSScriptRoot "..\..\src\cli.mjs"
|
|
34
|
+
|
|
35
|
+
if ($Clean) {
|
|
36
|
+
node $cli clean
|
|
37
|
+
exit $LASTEXITCODE
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
Write-Host ""
|
|
41
|
+
Write-Host " Checking the target before running..." -ForegroundColor Cyan
|
|
42
|
+
node $cli doctor
|
|
43
|
+
if ($LASTEXITCODE -ne 0) {
|
|
44
|
+
Write-Host ""
|
|
45
|
+
Write-Host " doctor says not ready — stopping here rather than burning a run." -ForegroundColor Yellow
|
|
46
|
+
exit 1
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
node $cli run --agents $Agents --minutes $Minutes
|
|
50
|
+
$code = $LASTEXITCODE
|
|
51
|
+
|
|
52
|
+
# A run exits non-zero when it FINDS something. That is the tool working, not
|
|
53
|
+
# the tool failing, so say so rather than leaving a bare red exit code.
|
|
54
|
+
Write-Host ""
|
|
55
|
+
if ($code -eq 0) {
|
|
56
|
+
Write-Host " Run finished with no failures." -ForegroundColor Green
|
|
57
|
+
} else {
|
|
58
|
+
Write-Host " Run finished and found problems — see the report above." -ForegroundColor Yellow
|
|
59
|
+
Write-Host " That is the expected outcome when there is something to find." -ForegroundColor DarkGray
|
|
60
|
+
}
|
|
61
|
+
exit $code
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
// A pretend app, so Populace can be evaluated in ten seconds with no backend.
|
|
2
|
+
//
|
|
3
|
+
// It stores everything in memory and fakes plausible network latency. One
|
|
4
|
+
// endpoint is deliberately slow and one deliberately flaky, so the report has
|
|
5
|
+
// something real to say — which is the point of the demo. A demo where
|
|
6
|
+
// everything passes teaches you nothing about what the tool is for.
|
|
7
|
+
|
|
8
|
+
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
9
|
+
const latency = (base, spread) => sleep(base + Math.random() * spread);
|
|
10
|
+
|
|
11
|
+
export function createAdapter() {
|
|
12
|
+
const db = { users: new Map(), posts: [], likes: 0, comments: 0, messages: 0, groups: [{ id: "riders" }, { id: "couriers" }] };
|
|
13
|
+
let likeCalls = 0;
|
|
14
|
+
|
|
15
|
+
return {
|
|
16
|
+
name: "demo",
|
|
17
|
+
|
|
18
|
+
async healthCheck() {
|
|
19
|
+
await latency(20, 30);
|
|
20
|
+
},
|
|
21
|
+
|
|
22
|
+
async createUser({ name, phone }) {
|
|
23
|
+
await latency(80, 120);
|
|
24
|
+
const id = `u_${phone}`;
|
|
25
|
+
db.users.set(id, { id, name });
|
|
26
|
+
return { id, name };
|
|
27
|
+
},
|
|
28
|
+
|
|
29
|
+
async setProfile() {
|
|
30
|
+
await latency(30, 40);
|
|
31
|
+
},
|
|
32
|
+
|
|
33
|
+
async reportLocation() {
|
|
34
|
+
await latency(15, 25);
|
|
35
|
+
},
|
|
36
|
+
|
|
37
|
+
async post(user, text) {
|
|
38
|
+
await latency(40, 60);
|
|
39
|
+
const post = { id: `p${db.posts.length}`, userId: user.id, text };
|
|
40
|
+
db.posts.push(post);
|
|
41
|
+
return post.id;
|
|
42
|
+
},
|
|
43
|
+
|
|
44
|
+
// Deliberately the slow one: an unindexed feed query is the single most
|
|
45
|
+
// common thing a simulation like this uncovers.
|
|
46
|
+
async recentPostsByOthers(user, limit = 10) {
|
|
47
|
+
await latency(180, 320);
|
|
48
|
+
return db.posts.filter((p) => p.userId !== user.id).slice(-limit);
|
|
49
|
+
},
|
|
50
|
+
|
|
51
|
+
// Deliberately the broken one: a permission rule that rejects some writes.
|
|
52
|
+
async like() {
|
|
53
|
+
await latency(25, 35);
|
|
54
|
+
likeCalls += 1;
|
|
55
|
+
if (likeCalls % 4 === 0) {
|
|
56
|
+
throw new Error(`new row violates row-level security policy "post_likes_insert"`);
|
|
57
|
+
}
|
|
58
|
+
db.likes += 1;
|
|
59
|
+
},
|
|
60
|
+
|
|
61
|
+
async comment() {
|
|
62
|
+
await latency(35, 45);
|
|
63
|
+
db.comments += 1;
|
|
64
|
+
},
|
|
65
|
+
|
|
66
|
+
async openConversation() {
|
|
67
|
+
await latency(50, 70);
|
|
68
|
+
return "thread_1";
|
|
69
|
+
},
|
|
70
|
+
|
|
71
|
+
async sendMessage() {
|
|
72
|
+
await latency(30, 40);
|
|
73
|
+
db.messages += 1;
|
|
74
|
+
},
|
|
75
|
+
|
|
76
|
+
async listGroups() {
|
|
77
|
+
await latency(25, 30);
|
|
78
|
+
return db.groups;
|
|
79
|
+
},
|
|
80
|
+
|
|
81
|
+
async joinGroup() {
|
|
82
|
+
await latency(40, 50);
|
|
83
|
+
},
|
|
84
|
+
|
|
85
|
+
async deleteUser(user) {
|
|
86
|
+
await latency(60, 80);
|
|
87
|
+
db.users.delete(user.id);
|
|
88
|
+
},
|
|
89
|
+
};
|
|
90
|
+
}
|