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 +39 -0
- package/index.js +16 -1
- package/package.json +1 -1
- package/test/root-listing.test.js +26 -0
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.
|
|
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
|