mikser-io-drive 11.3.0 → 11.3.1

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/README.md CHANGED
@@ -353,6 +353,45 @@ class of problem from Nephele's four known gaps, and it carries 65 open issues
353
353
  against Nephele's zero. Nephele is also Apache-2.0 and load-bearing for its
354
354
  author's own product, which is the maintenance signal that matters most.
355
355
 
356
+ **Writing it here was considered and is not close.** RFC 4918 is about 4,650
357
+ lines in Nephele — 2,994 of method handlers and 1,662 of machinery — and the
358
+ weight is not where it looks:
359
+
360
+ | | lines | |
361
+ | --- | --- | --- |
362
+ | `Method.js` | 930 | the base class every method inherits |
363
+ | — `checkIfHeader` | 234 | the `If:` header alone |
364
+ | — `checkConditionalHeaders` | 77 | |
365
+ | `LOCK` + `UNLOCK` | 442 | plus 81 lock references threaded through `Method.js` |
366
+ | `MOVE` + `COPY` | 438 | `Depth` and `Overwrite` semantics |
367
+ | `PROPFIND` | 276 | |
368
+ | `GET`/`HEAD` | 286 | ranges, etags |
369
+
370
+ Listing files is the cheap part. `If:` is a grammar rather than a value —
371
+ tagged and untagged lists, `Not`, lock tokens and etags mixed, evaluated
372
+ against resources other than the request target — and getting it wrong means
373
+ Finder's Save As either walks over somebody's lock or refuses forever. Locking
374
+ is not isolated in `LOCK.js` either: provisional locks, timeouts, per-user lock
375
+ permission and token extraction run on every write method.
376
+
377
+ And the litmus column above is what would be given up: 95 of 104 on
378
+ `meta-files`, with four known gaps named and pinned. A hand-written server
379
+ starts at zero and earns each point by a client breaking in someone's hands.
380
+
381
+ The read-only case is genuinely different — `OPTIONS` + `PROPFIND` + `GET` with
382
+ no locking and no conditionals is a few hundred lines, and the drive root in
383
+ `lib/root-listing.js` is already that shape. It is not the case anyone is
384
+ asking for.
385
+
386
+ **The sharper version of the question is the ADAPTER, not the engine.** Three
387
+ things have been worked around here, and all three are in
388
+ `@nephele/adapter-file-system` rather than in the protocol: `displayname`
389
+ commented out of its live-property list with a TODO, a flat `max-age=604800` on
390
+ the OPTIONS response, and a read-only mount advertising `PUT` in `Allow`. The
391
+ adapter answers "what is at this path and what are its properties", which is
392
+ the thin half. If it keeps costing, replace it and keep the 4,650 lines that
393
+ matter.
394
+
356
395
  Staying on Nephele. If its gaps ever become the binding constraint, the
357
396
  serious alternative is not another npm package — it is **Apache `mod_dav`**,
358
397
  the implementation litmus was written to test, which reads `htpasswd` and
package/index.js CHANGED
@@ -47,9 +47,12 @@ export const writeCapability = (name) => `drive:${name}:write`
47
47
  * data: { folder: 'data', readOnly: true },
48
48
  * Reports: { folder: 'reports' }, // shows as "Reports"
49
49
  * q3: { folder: 'q3', displayName: 'Q3 reports' },
50
- * share: { folder: 'share', host: 'drive.example.com' },
51
50
  * },
52
51
  * auth: identity,
52
+ * // A domain of its own, where `/` lists the endpoints the caller
53
+ * // may read. Belongs to the DRIVE, not to one endpoint: the whole
54
+ * // point is that several endpoints share it.
55
+ * host: 'drive.example.com',
53
56
  * })
54
57
  *
55
58
  * One Nephele server per endpoint, mounted at `<base>/<name>` — the same
@@ -145,6 +148,18 @@ export function drive(options = {}) {
145
148
  if (!ep.folder) {
146
149
  throw new Error(`drive: endpoint ${JSON.stringify(name)} declares no folder`)
147
150
  }
151
+ // `host` belongs to the drive, not to an endpoint — several
152
+ // endpoints share one domain, and `/` on it lists them. Said
153
+ // out loud because the alternative is a config that reads as
154
+ // though it works and does nothing: this option was
155
+ // per-endpoint while it was being designed, and the example in
156
+ // this file's own JSDoc showed it that way after it moved.
157
+ if (ep.host) {
158
+ logger.warn(
159
+ 'drive: endpoint %j sets `host`, which is a plugin-level option and is ignored here. '
160
+ + 'Move it beside `endpoints`: drive({ host: %j, endpoints: { … } }).',
161
+ name, ep.host)
162
+ }
148
163
  const root = resolve(ep.folder)
149
164
  const mountPath = `${base}/${name}`
150
165
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mikser-io-drive",
3
- "version": "11.3.0",
3
+ "version": "11.3.1",
4
4
  "description": "WebDAV for mikser-io. Exposes working-folder directories as WebDAV endpoints via Nephele, authenticated through mikser-io-auth's htpasswd identity — so content can be edited from Finder, Explorer or any DAV client and the build picks the change up.",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -113,6 +113,32 @@ after(async () => {
113
113
  await rm(dir, { recursive: true, force: true })
114
114
  })
115
115
 
116
+ describe('host in the wrong place', () => {
117
+ // It was per-endpoint while this was being designed, and moved to the
118
+ // plugin when the root listing arrived — several endpoints share one
119
+ // domain, and `/` on it lists them. This file's own JSDoc went on showing
120
+ // the old shape, which is a config that reads as though it works and does
121
+ // nothing at all. So it says so.
122
+ it('warns instead of silently ignoring it', async () => {
123
+ const said = []
124
+ const core = {
125
+ runtime,
126
+ onLoad: () => {},
127
+ onLoaded: (cb) => said.push(cb),
128
+ useLogger: () => ({
129
+ info(){}, error(){}, debug(){}, trace(){},
130
+ warn: (...args) => said.push(args.join(' ')),
131
+ }),
132
+ }
133
+ drive({ endpoints: { Share: { folder: 'documents', host: 'drive.example.test' } } })(core)
134
+ for (const entry of [...said]) if (typeof entry === 'function') await entry()
135
+
136
+ const warning = said.find(entry =>
137
+ typeof entry === 'string' && entry.includes('plugin-level option'))
138
+ assert.ok(warning, `no warning about a misplaced host:\n${said.filter(e => typeof e === 'string').join('\n')}`)
139
+ })
140
+ })
141
+
116
142
  describe('the drive root on its own host', () => {
117
143
  it('announces itself as WebDAV, which is what the redirector asks first', async () => {
118
144
  // Answered without credentials on purpose: this is discovery, and it