braid-text 0.2.72 → 0.2.74

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.
@@ -2,7 +2,9 @@
2
2
  "permissions": {
3
3
  "allow": [
4
4
  "Bash(node test/test.js:*)",
5
- "Bash(node:*)"
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)\")"
6
8
  ],
7
9
  "deny": [],
8
10
  "ask": []
package/AI-README.md ADDED
@@ -0,0 +1,81 @@
1
+ # AI-README for braid-text
2
+
3
+ This document provides AI assistants with key information about the braid-text project, including development procedures and architecture notes.
4
+
5
+ ## Release Procedure
6
+
7
+ Follow these steps to create a new release:
8
+
9
+ 1. **Version Bump**
10
+ - Update the version in `package.json` (use smallest version bump: patch level)
11
+ - Current version format: `0.2.x`
12
+
13
+ 2. **Run Tests**
14
+ - Run `node test/test.js` - should show all 74 tests passing
15
+ - Run `node test/fuzz-test.js` - should run without errors (best_n = Infinity @ NaN indicates success)
16
+ - Both tests must pass before proceeding
17
+
18
+ 3. **Commit Changes**
19
+ - Use commit message format: `VERSION - description`
20
+ - Example: `0.2.73 - adds automatic test cleanup`
21
+ - Keep description concise and descriptive
22
+
23
+ 4. **Push to Remote**
24
+ - Run `git push` to push to the remote repository
25
+
26
+ 5. **Publish to npm**
27
+ - Run `npm publish` to publish the new version
28
+
29
+ ## Test Suite
30
+
31
+ ### test/test.js
32
+ - Main test suite with 74 tests
33
+ - Tests Braid protocol, version control, syncing, and collaboration features
34
+ - Automatically cleans up `test_db_folder` after completion
35
+ - Run with: `node test/test.js`
36
+ - Filter tests with: `node test/test.js --filter="sync"`
37
+
38
+ ### test/fuzz-test.js
39
+ - Fuzz testing suite that generates random edits and verifies correctness
40
+ - Tests diamond-types integration and merge operations
41
+ - Runs 10,000 iterations by default
42
+ - Success indicated by `best_n = Infinity @ NaN` (no failures found)
43
+ - Run with: `node test/fuzz-test.js`
44
+
45
+ ## Project Structure
46
+
47
+ - **index.js** - Main library file implementing Braid protocol for collaborative text
48
+ - **package.json** - Package configuration and dependencies
49
+ - **test/** - Test suite directory
50
+ - **test.js** - Main test runner (supports both console and browser modes)
51
+ - **tests.js** - Test definitions (shared between console and browser)
52
+ - **fuzz-test.js** - Fuzz testing suite
53
+ - **test.html** - Browser test interface
54
+
55
+ ## Key Dependencies
56
+
57
+ - **@braid.org/diamond-types-node** - CRDT implementation for conflict-free text editing
58
+ - **braid-http** - Braid protocol HTTP implementation
59
+ - **url-file-db** - File-based database for persistent storage
60
+
61
+ ## Architecture Notes
62
+
63
+ - Implements the Braid protocol for synchronizing collaborative text over HTTP
64
+ - Uses diamond-types CRDT for conflict-free merging
65
+ - Supports both simpleton and dt (diamond-types) merge strategies
66
+ - Provides version control with parents tracking and version history
67
+ - File-based persistence with case-insensitive filesystem support
68
+
69
+ ## Common Operations
70
+
71
+ ### Running Tests Locally
72
+ ```bash
73
+ node test/test.js # Run all tests
74
+ node test/test.js --filter="sync" # Run tests matching "sync"
75
+ node test/fuzz-test.js # Run fuzz tests
76
+ ```
77
+
78
+ ### Test Cleanup
79
+ The test suite automatically cleans up temporary files and databases. If manual cleanup is needed:
80
+ - Test database is created at `test/test_db_folder` during tests
81
+ - Automatically removed after test completion
package/index.js CHANGED
@@ -304,6 +304,17 @@ function create_braid_text() {
304
304
  if (!req.subscribe) {
305
305
  res.setHeader("Accept-Subscribe", "true")
306
306
 
307
+ // Set headers based on request type
308
+ // Current-Version: always for dt encoding, or when version/parents present
309
+ if (req.headers['accept-transfer-encoding'] === 'dt' || req.version || req.parents) {
310
+ res.setHeader("Current-Version", get_current_version())
311
+ }
312
+
313
+ // Merge-Type: only when version/parents present
314
+ if (req.version || req.parents) {
315
+ res.setHeader("Merge-Type", merge_type)
316
+ }
317
+
307
318
  // special case for HEAD asking for version/parents,
308
319
  // to be faster by not reconstructing body
309
320
  if (req.method === "HEAD" && (req.version || req.parents))
@@ -321,13 +332,10 @@ function create_braid_text() {
321
332
  }
322
333
 
323
334
  if (req.headers['accept-transfer-encoding'] === 'dt') {
324
- res.setHeader("Current-Version", get_current_version())
325
335
  res.setHeader("X-Transfer-Encoding", 'dt')
326
336
  res.setHeader("Content-Length", x.body.length)
327
337
  return my_end(209, req.method === "HEAD" ? null : x.body, 'Multiresponse')
328
338
  } else {
329
- if (req.version || req.parents)
330
- res.setHeader("Current-Version", get_current_version())
331
339
  res.setHeader("Version", ascii_ify(x.version.map((x) => JSON.stringify(x)).join(", ")))
332
340
  var buffer = Buffer.from(x.body, "utf8")
333
341
  res.setHeader("Repr-Digest", get_digest(buffer))
@@ -2208,7 +2216,7 @@ function create_braid_text() {
2208
2216
  }
2209
2217
 
2210
2218
  var {
2211
- encode_file_path_component, ensure_unique_case_insensitive_path_component
2219
+ encode_file_path_component, encode_to_avoid_icase_collision
2212
2220
  } = require('url-file-db/canonical_path')
2213
2221
 
2214
2222
  // Mapping between keys and their encoded filenames
@@ -2229,7 +2237,7 @@ function create_braid_text() {
2229
2237
  var encoded = encode_file_path_component(swapped)
2230
2238
 
2231
2239
  // Resolve case collisions for case-insensitive filesystems (Mac/Windows)
2232
- encoded = ensure_unique_case_insensitive_path_component(encoded, ifilenames)
2240
+ encoded = encode_to_avoid_icase_collision(encoded, ifilenames)
2233
2241
 
2234
2242
  // Cache the mapping
2235
2243
  key_to_filename.set(key, encoded)
@@ -2258,7 +2266,13 @@ function create_braid_text() {
2258
2266
  if (!key_to_filename.has(key)) {
2259
2267
  key_to_filename.set(key, encoded)
2260
2268
  ifilenames.add(encoded.toLowerCase())
2261
- } else throw new Error('filename conflict detected')
2269
+ } else {
2270
+ // Already have this key mapped - verify it maps to the same encoding
2271
+ if (key_to_filename.get(key) !== encoded) {
2272
+ throw new Error(`filename conflict detected: key "${key}" maps to both "${key_to_filename.get(key)}" and "${encoded}"`)
2273
+ }
2274
+ // Otherwise it's just a re-initialization, skip it
2275
+ }
2262
2276
  }
2263
2277
  }
2264
2278
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "braid-text",
3
- "version": "0.2.72",
3
+ "version": "0.2.74",
4
4
  "description": "Library for collaborative text over http using braid.",
5
5
  "author": "Braid Working Group",
6
6
  "repository": "braid-org/braid-text",
@@ -8,6 +8,6 @@
8
8
  "dependencies": {
9
9
  "@braid.org/diamond-types-node": "^2.0.0",
10
10
  "braid-http": "~1.3.83",
11
- "url-file-db": "^0.0.10"
11
+ "url-file-db": "^0.0.19"
12
12
  }
13
13
  }
package/test/tests.js CHANGED
@@ -2146,6 +2146,59 @@ runTest(
2146
2146
  'ok'
2147
2147
  )
2148
2148
 
2149
+ runTest(
2150
+ "test Merge-Type header per Braid spec",
2151
+ async () => {
2152
+ let key = 'test-merge-type-' + Math.random().toString(36).slice(2)
2153
+
2154
+ // First PUT some content (15 characters, so version is alice-14)
2155
+ await braid_fetch(`/${key}`, {
2156
+ method: 'PUT',
2157
+ version: ['alice-14'],
2158
+ parents: [],
2159
+ body: 'initial content'
2160
+ })
2161
+
2162
+ // Test 1: GET with Version header should include Merge-Type
2163
+ let r1 = await braid_fetch(`/${key}`, {
2164
+ version: ['alice-14']
2165
+ })
2166
+ if (!r1.headers.get('merge-type')) return 'Missing Merge-Type with Version header'
2167
+
2168
+ // Test 2: GET with empty Version header should still include Merge-Type
2169
+ let r2 = await braid_fetch(`/${key}`, {
2170
+ headers: {
2171
+ 'Version': ''
2172
+ }
2173
+ })
2174
+ if (!r2.headers.get('merge-type')) return 'Missing Merge-Type with empty Version header'
2175
+
2176
+ // Test 3: GET with Parents header should include Merge-Type
2177
+ let r3 = await braid_fetch(`/${key}`, {
2178
+ parents: []
2179
+ })
2180
+ if (!r3.headers.get('merge-type')) return 'Missing Merge-Type with Parents header'
2181
+
2182
+ // Test 4: Regular GET without Version/Parents should NOT include Merge-Type
2183
+ let r4 = await braid_fetch(`/${key}`)
2184
+ if (r4.headers.get('merge-type')) return 'Unexpected Merge-Type without Version/Parents'
2185
+
2186
+ // Test 5: GET with Subscribe should include Merge-Type
2187
+ let a = new AbortController()
2188
+ let r5 = await braid_fetch(`/${key}`, {
2189
+ signal: a.signal,
2190
+ subscribe: true
2191
+ })
2192
+ if (!r5.headers.get('merge-type')) return 'Missing Merge-Type with Subscribe'
2193
+
2194
+ // Close subscription
2195
+ a.abort()
2196
+
2197
+ return 'ok'
2198
+ },
2199
+ 'ok'
2200
+ )
2201
+
2149
2202
  runTest(
2150
2203
  "test filename conflict detection (different encodings of same key)",
2151
2204
  async () => {