braid-text 0.2.78 → 0.2.79
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/index.js +11 -13
- package/package.json +1 -1
- package/test/test.js +306 -0
- package/test/tests.js +3 -10
- package/.claude/settings.local.json +0 -14
package/index.js
CHANGED
|
@@ -99,6 +99,13 @@ function create_braid_text() {
|
|
|
99
99
|
var local_first_put
|
|
100
100
|
var local_first_put_promise = new Promise(done => local_first_put = done)
|
|
101
101
|
|
|
102
|
+
function handle_error(_e) {
|
|
103
|
+
if (closed) return
|
|
104
|
+
disconnect()
|
|
105
|
+
console.log(`disconnected, retrying in 1 second`)
|
|
106
|
+
setTimeout(connect, 1000)
|
|
107
|
+
}
|
|
108
|
+
|
|
102
109
|
connect()
|
|
103
110
|
async function connect() {
|
|
104
111
|
if (options.on_connect) options.on_connect()
|
|
@@ -180,6 +187,7 @@ function create_braid_text() {
|
|
|
180
187
|
await braid_text.put(a, update)
|
|
181
188
|
extend_fork_point(update)
|
|
182
189
|
},
|
|
190
|
+
on_error: handle_error
|
|
183
191
|
}
|
|
184
192
|
// Handle case where remote doesn't exist yet - wait for local to create it
|
|
185
193
|
var remote_result = await braid_text.get(b, b_ops)
|
|
@@ -189,13 +197,9 @@ function create_braid_text() {
|
|
|
189
197
|
disconnect()
|
|
190
198
|
connect()
|
|
191
199
|
}
|
|
192
|
-
//
|
|
200
|
+
// on_error will call handle_error when connection drops
|
|
193
201
|
} catch (e) {
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
disconnect()
|
|
197
|
-
console.log(`disconnected, retrying in 1 second`)
|
|
198
|
-
setTimeout(connect, 1000)
|
|
202
|
+
handle_error(e)
|
|
199
203
|
}
|
|
200
204
|
}
|
|
201
205
|
}
|
|
@@ -533,19 +537,13 @@ function create_braid_text() {
|
|
|
533
537
|
if (res.status === 404) return null
|
|
534
538
|
|
|
535
539
|
if (options.subscribe) {
|
|
536
|
-
if (options.dont_retry) {
|
|
537
|
-
var error_happened
|
|
538
|
-
var error_promise = new Promise((_, fail) => error_happened = fail)
|
|
539
|
-
}
|
|
540
|
-
|
|
541
540
|
res.subscribe(async update => {
|
|
542
541
|
update.body = update.body_text
|
|
543
542
|
if (update.patches)
|
|
544
543
|
for (var p of update.patches) p.content = p.content_text
|
|
545
544
|
await options.subscribe(update)
|
|
546
|
-
}, e => options.
|
|
545
|
+
}, e => options.on_error?.(e))
|
|
547
546
|
|
|
548
|
-
if (options.dont_retry) return await error_promise
|
|
549
547
|
return res
|
|
550
548
|
} else return await res.text()
|
|
551
549
|
}
|
package/package.json
CHANGED
package/test/test.js
ADDED
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// Unified test runner - can run in console mode (Node.js) or browser mode (server)
|
|
4
|
+
const http = require('http')
|
|
5
|
+
const {fetch: braid_fetch} = require('braid-http')
|
|
6
|
+
const defineTests = require('./tests.js')
|
|
7
|
+
|
|
8
|
+
// Parse command line arguments
|
|
9
|
+
const args = process.argv.slice(2)
|
|
10
|
+
const mode = args.includes('--browser') || args.includes('-b') ? 'browser' : 'console'
|
|
11
|
+
const portArg = args.find(arg => arg.startsWith('--port='))?.split('=')[1]
|
|
12
|
+
|| args.find(arg => !arg.startsWith('-') && !isNaN(arg))
|
|
13
|
+
const port = portArg || 8889
|
|
14
|
+
const filterArg = args.find(arg => arg.startsWith('--filter='))?.split('=')[1]
|
|
15
|
+
|| args.find(arg => arg.startsWith('--grep='))?.split('=')[1]
|
|
16
|
+
|
|
17
|
+
// Show help if requested
|
|
18
|
+
if (args.includes('--help') || args.includes('-h')) {
|
|
19
|
+
console.log(`
|
|
20
|
+
Usage: node test.js [options]
|
|
21
|
+
|
|
22
|
+
Options:
|
|
23
|
+
--browser, -b Start server for browser testing (default: console mode)
|
|
24
|
+
--port=PORT Specify port number (default: 8889)
|
|
25
|
+
PORT Port number as positional argument
|
|
26
|
+
--filter=PATTERN Only run tests matching pattern (case-insensitive)
|
|
27
|
+
--grep=PATTERN Alias for --filter
|
|
28
|
+
--help, -h Show this help message
|
|
29
|
+
|
|
30
|
+
Examples:
|
|
31
|
+
node test.js # Run all tests in console
|
|
32
|
+
node test.js --filter="sync" # Run only tests with "sync" in name
|
|
33
|
+
node test.js --grep="digest" # Run only tests with "digest" in name
|
|
34
|
+
node test.js --browser # Start browser test server
|
|
35
|
+
node test.js --browser --port=9000
|
|
36
|
+
node test.js -b 9000 # Short form with port
|
|
37
|
+
`)
|
|
38
|
+
process.exit(0)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// ============================================================================
|
|
42
|
+
// Shared Server Code
|
|
43
|
+
// ============================================================================
|
|
44
|
+
|
|
45
|
+
function createTestServer(options = {}) {
|
|
46
|
+
const {
|
|
47
|
+
port = 8889,
|
|
48
|
+
runTests = false,
|
|
49
|
+
logRequests = false
|
|
50
|
+
} = options
|
|
51
|
+
|
|
52
|
+
const braid_text = require(`${__dirname}/../index.js`)
|
|
53
|
+
braid_text.db_folder = `${__dirname}/test_db_folder`
|
|
54
|
+
|
|
55
|
+
const braid_text2 = braid_text.create_braid_text()
|
|
56
|
+
braid_text2.db_folder = null
|
|
57
|
+
|
|
58
|
+
const server = http.createServer(async (req, res) => {
|
|
59
|
+
if (logRequests) {
|
|
60
|
+
console.log(`${req.method} ${req.url}`)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Free the CORS
|
|
64
|
+
braid_text.free_cors(res)
|
|
65
|
+
if (req.method === 'OPTIONS') return
|
|
66
|
+
|
|
67
|
+
if (req.url.startsWith('/have_error')) {
|
|
68
|
+
res.statusCode = 569
|
|
69
|
+
return res.end('error')
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (req.url.startsWith('/404')) {
|
|
73
|
+
res.statusCode = 404
|
|
74
|
+
return res.end('Not Found')
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (req.url.startsWith('/eval')) {
|
|
78
|
+
var body = await new Promise(done => {
|
|
79
|
+
var chunks = []
|
|
80
|
+
req.on('data', chunk => chunks.push(chunk))
|
|
81
|
+
req.on('end', () => done(Buffer.concat(chunks)))
|
|
82
|
+
})
|
|
83
|
+
try {
|
|
84
|
+
eval('' + body)
|
|
85
|
+
} catch (error) {
|
|
86
|
+
res.writeHead(500, { 'Content-Type': 'text/plain' })
|
|
87
|
+
res.end(`Error: ${error.message}`)
|
|
88
|
+
}
|
|
89
|
+
return
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (req.url.startsWith('/test.html')) {
|
|
93
|
+
let parts = req.url.split(/[\?&=]/g)
|
|
94
|
+
|
|
95
|
+
if (parts[1] === 'check') {
|
|
96
|
+
res.writeHead(200, { "Content-Type": "application/json", "Cache-Control": "no-cache" })
|
|
97
|
+
return res.end(JSON.stringify({
|
|
98
|
+
checking: parts[2],
|
|
99
|
+
result: (await braid_text.get(parts[2])) != null
|
|
100
|
+
}))
|
|
101
|
+
} else if (parts[1] === 'dt_create_bytes_big_name') {
|
|
102
|
+
try {
|
|
103
|
+
braid_text.dt_create_bytes('x'.repeat(1000000) + '-0', [], 0, 0, 'hi')
|
|
104
|
+
return res.end(JSON.stringify({ ok: true }))
|
|
105
|
+
} catch (e) {
|
|
106
|
+
return res.end(JSON.stringify({ ok: false, error: '' + e }))
|
|
107
|
+
}
|
|
108
|
+
} else if (parts[1] === 'dt_create_bytes_many_names') {
|
|
109
|
+
try {
|
|
110
|
+
braid_text.dt_create_bytes('hi-0', new Array(1000000).fill(0).map((x, i) => `x${i}-0`), 0, 0, 'hi')
|
|
111
|
+
return res.end(JSON.stringify({ ok: true }))
|
|
112
|
+
} catch (e) {
|
|
113
|
+
return res.end(JSON.stringify({ ok: false, error: '' + e }))
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8", "Cache-Control": "no-cache" })
|
|
118
|
+
require("fs").createReadStream(`${__dirname}/test.html`).pipe(res)
|
|
119
|
+
return
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Serve tests.js file for browser
|
|
123
|
+
if (req.url.startsWith('/tests.js')) {
|
|
124
|
+
res.writeHead(200, { "Content-Type": "application/javascript; charset=utf-8", "Cache-Control": "no-cache" })
|
|
125
|
+
require("fs").createReadStream(`${__dirname}/tests.js`).pipe(res)
|
|
126
|
+
return
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Now serve the collaborative text!
|
|
130
|
+
braid_text.serve(req, res)
|
|
131
|
+
})
|
|
132
|
+
|
|
133
|
+
return {
|
|
134
|
+
server,
|
|
135
|
+
start: () => new Promise((resolve) => {
|
|
136
|
+
server.listen(port, 'localhost', () => {
|
|
137
|
+
if (runTests) {
|
|
138
|
+
console.log(`Test server running on http://localhost:${port}`)
|
|
139
|
+
} else {
|
|
140
|
+
console.log(`serving: http://localhost:${port}/test.html`)
|
|
141
|
+
}
|
|
142
|
+
resolve()
|
|
143
|
+
})
|
|
144
|
+
}),
|
|
145
|
+
port
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// ============================================================================
|
|
150
|
+
// Console Test Mode (Node.js)
|
|
151
|
+
// ============================================================================
|
|
152
|
+
|
|
153
|
+
async function runConsoleTests() {
|
|
154
|
+
// Test tracking
|
|
155
|
+
let totalTests = 0
|
|
156
|
+
let passedTests = 0
|
|
157
|
+
let failedTests = 0
|
|
158
|
+
|
|
159
|
+
// Handle unhandled rejections during tests (some tests intentionally cause errors)
|
|
160
|
+
const unhandledRejections = []
|
|
161
|
+
process.on('unhandledRejection', (reason, promise) => {
|
|
162
|
+
// Collect but don't crash - some tests intentionally trigger errors
|
|
163
|
+
unhandledRejections.push({ reason, promise })
|
|
164
|
+
})
|
|
165
|
+
|
|
166
|
+
// Node.js test runner implementation
|
|
167
|
+
// Store tests to run sequentially instead of in parallel
|
|
168
|
+
const testsToRun = []
|
|
169
|
+
|
|
170
|
+
function runTest(testName, testFunction, expectedResult) {
|
|
171
|
+
// Apply filter if specified
|
|
172
|
+
if (filterArg && !testName.toLowerCase().includes(filterArg.toLowerCase())) {
|
|
173
|
+
return // Skip this test
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
totalTests++
|
|
177
|
+
testsToRun.push({ testName, testFunction, expectedResult })
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Create a braid_fetch wrapper that points to localhost
|
|
181
|
+
function createBraidFetch(baseUrl) {
|
|
182
|
+
return async (url, options = {}) => {
|
|
183
|
+
const fullUrl = url.startsWith('http') ? url : `${baseUrl}${url}`
|
|
184
|
+
return braid_fetch(fullUrl, options)
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
console.log('Starting braid-text tests...\n')
|
|
189
|
+
|
|
190
|
+
// Create and start the test server
|
|
191
|
+
const testServer = createTestServer({
|
|
192
|
+
port,
|
|
193
|
+
runTests: true,
|
|
194
|
+
logRequests: false
|
|
195
|
+
})
|
|
196
|
+
|
|
197
|
+
await testServer.start()
|
|
198
|
+
|
|
199
|
+
// Create braid_fetch bound to test server
|
|
200
|
+
const testBraidFetch = createBraidFetch(`http://localhost:${port}`)
|
|
201
|
+
|
|
202
|
+
// Load the real diamond-types module for Node.js
|
|
203
|
+
const { Doc, OpLog } = require('@braid.org/diamond-types-node')
|
|
204
|
+
|
|
205
|
+
// Define globals needed for some tests
|
|
206
|
+
global.Doc = Doc
|
|
207
|
+
global.OpLog = OpLog
|
|
208
|
+
global.dt_p = Promise.resolve() // No initialization needed for Node.js version
|
|
209
|
+
global.fetch = testBraidFetch
|
|
210
|
+
global.AbortController = AbortController
|
|
211
|
+
global.crypto = require('crypto').webcrypto
|
|
212
|
+
|
|
213
|
+
// Run all tests
|
|
214
|
+
defineTests(runTest, testBraidFetch)
|
|
215
|
+
|
|
216
|
+
// Run tests sequentially (not in parallel) to avoid conflicts
|
|
217
|
+
for (const { testName, testFunction, expectedResult } of testsToRun) {
|
|
218
|
+
try {
|
|
219
|
+
const result = await testFunction()
|
|
220
|
+
if (result == expectedResult) {
|
|
221
|
+
passedTests++
|
|
222
|
+
console.log(`✓ ${testName}`)
|
|
223
|
+
} else {
|
|
224
|
+
failedTests++
|
|
225
|
+
console.log(`✗ ${testName}`)
|
|
226
|
+
console.log(` Expected: ${expectedResult}`)
|
|
227
|
+
console.log(` Got: ${result}`)
|
|
228
|
+
}
|
|
229
|
+
} catch (error) {
|
|
230
|
+
failedTests++
|
|
231
|
+
console.log(`✗ ${testName}`)
|
|
232
|
+
console.log(` Error: ${error.message || error}`)
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// Print summary
|
|
237
|
+
console.log('\n' + '='.repeat(50))
|
|
238
|
+
console.log(`Total: ${totalTests} | Passed: ${passedTests} | Failed: ${failedTests}`)
|
|
239
|
+
console.log('='.repeat(50))
|
|
240
|
+
|
|
241
|
+
// Clean up test database folder
|
|
242
|
+
console.log('Cleaning up test database folder...')
|
|
243
|
+
const fs = require('fs')
|
|
244
|
+
const path = require('path')
|
|
245
|
+
const testDbPath = path.join(__dirname, 'test_db_folder')
|
|
246
|
+
try {
|
|
247
|
+
if (fs.existsSync(testDbPath)) {
|
|
248
|
+
fs.rmSync(testDbPath, { recursive: true, force: true })
|
|
249
|
+
console.log('Test database folder removed')
|
|
250
|
+
}
|
|
251
|
+
} catch (err) {
|
|
252
|
+
console.log(`Warning: Could not remove test database folder: ${err.message}`)
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// Force close the server and all connections
|
|
256
|
+
console.log('Closing server...')
|
|
257
|
+
testServer.server.close(() => {
|
|
258
|
+
console.log('Server closed callback - calling process.exit()')
|
|
259
|
+
process.exit(failedTests > 0 ? 1 : 0)
|
|
260
|
+
})
|
|
261
|
+
|
|
262
|
+
// Also close all active connections if the method exists (Node 18.2+)
|
|
263
|
+
if (typeof testServer.server.closeAllConnections === 'function') {
|
|
264
|
+
console.log('Closing all connections...')
|
|
265
|
+
testServer.server.closeAllConnections()
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// Fallback: force exit after a short delay even if server hasn't fully closed
|
|
269
|
+
console.log('Setting 200ms timeout fallback...')
|
|
270
|
+
setTimeout(() => {
|
|
271
|
+
console.log('Timeout reached - calling process.exit()')
|
|
272
|
+
process.exit(failedTests > 0 ? 1 : 0)
|
|
273
|
+
}, 200)
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// ============================================================================
|
|
277
|
+
// Browser Test Mode (Server)
|
|
278
|
+
// ============================================================================
|
|
279
|
+
|
|
280
|
+
async function runBrowserMode() {
|
|
281
|
+
const testServer = createTestServer({
|
|
282
|
+
port,
|
|
283
|
+
runTests: false,
|
|
284
|
+
logRequests: true
|
|
285
|
+
})
|
|
286
|
+
|
|
287
|
+
await testServer.start()
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// ============================================================================
|
|
291
|
+
// Main Entry Point
|
|
292
|
+
// ============================================================================
|
|
293
|
+
|
|
294
|
+
async function main() {
|
|
295
|
+
if (mode === 'browser') {
|
|
296
|
+
await runBrowserMode()
|
|
297
|
+
} else {
|
|
298
|
+
await runConsoleTests()
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// Run the appropriate mode
|
|
303
|
+
main().catch(err => {
|
|
304
|
+
console.error('Fatal error:', err)
|
|
305
|
+
process.exit(1)
|
|
306
|
+
})
|
package/test/tests.js
CHANGED
|
@@ -14,31 +14,24 @@ runTest(
|
|
|
14
14
|
})
|
|
15
15
|
if (!r.ok) return 'got: ' + r.status
|
|
16
16
|
|
|
17
|
-
var
|
|
17
|
+
var r1 = await braid_fetch(`/eval`, {
|
|
18
18
|
method: 'PUT',
|
|
19
19
|
body: `void (async () => {
|
|
20
20
|
var x = await new Promise(done => {
|
|
21
21
|
braid_text.get(new URL('http://localhost:8889/${key}'), {
|
|
22
22
|
subscribe: update => {
|
|
23
|
-
if (update.body_text === '
|
|
23
|
+
if (update.body_text === 'hi') done(update.body_text)
|
|
24
24
|
}
|
|
25
25
|
})
|
|
26
26
|
})
|
|
27
27
|
res.end(x)
|
|
28
28
|
})()`
|
|
29
29
|
})
|
|
30
|
-
|
|
31
|
-
var r2 = await braid_fetch(`/${key}`, {
|
|
32
|
-
method: 'PUT',
|
|
33
|
-
body: 'yo'
|
|
34
|
-
})
|
|
35
|
-
|
|
36
|
-
var r1 = await r1p
|
|
37
30
|
if (!r1.ok) return 'got: ' + r.status
|
|
38
31
|
|
|
39
32
|
return await r1.text()
|
|
40
33
|
},
|
|
41
|
-
'
|
|
34
|
+
'hi'
|
|
42
35
|
)
|
|
43
36
|
|
|
44
37
|
runTest(
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"permissions": {
|
|
3
|
-
"allow": [
|
|
4
|
-
"Bash(node test/test.js:*)",
|
|
5
|
-
"Bash(node:*)",
|
|
6
|
-
"Bash(git add:*)",
|
|
7
|
-
"Bash(git commit -m \"$(cat <<''EOF''\n0.2.74 - updates url-file-db to 0.0.19\n\n🤖 Generated with [Claude Code](https://claude.com/claude-code)\n\nCo-Authored-By: Claude <noreply@anthropic.com>\nEOF\n)\")",
|
|
8
|
-
"Bash(git push)",
|
|
9
|
-
"Bash(npm publish:*)"
|
|
10
|
-
],
|
|
11
|
-
"deny": [],
|
|
12
|
-
"ask": []
|
|
13
|
-
}
|
|
14
|
-
}
|