@quo-systems/dock 0.2.5 → 0.2.6
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/dist/harbor/disk.js +13 -0
- package/dist/harbor/resolve.d.ts +16 -0
- package/dist/harbor/resolve.js +17 -0
- package/harbor/disk.ts +13 -0
- package/harbor/quo-harbor.md +9 -0
- package/harbor/resolve.ts +31 -0
- package/package.json +3 -3
package/dist/harbor/disk.js
CHANGED
|
@@ -25,6 +25,7 @@ import { mkdir, readFile, writeFile, unlink, stat } from 'node:fs/promises';
|
|
|
25
25
|
import { existsSync } from 'node:fs';
|
|
26
26
|
import { join, resolve, isAbsolute } from 'node:path';
|
|
27
27
|
import { pathToFileURL } from 'node:url';
|
|
28
|
+
import { register } from 'node:module';
|
|
28
29
|
import process from 'node:process';
|
|
29
30
|
import { User, Desk, Avatar } from '../beings/index.js';
|
|
30
31
|
import { setup } from '../beings/setup.js';
|
|
@@ -35,12 +36,24 @@ export const BUILT_IN = { User, Desk, Avatar };
|
|
|
35
36
|
// a second harbor in the same pid would pass that check while still being a
|
|
36
37
|
// second ward with one pk.
|
|
37
38
|
const HELD = new Set();
|
|
39
|
+
// A class file in the harbor folder resolves its packages from where the
|
|
40
|
+
// dock is installed, `resolve.ts`: the folder has no node_modules and is not
|
|
41
|
+
// meant to. Registered once per process, before the first class loads.
|
|
42
|
+
let hooked = false;
|
|
43
|
+
function hook() {
|
|
44
|
+
if (hooked)
|
|
45
|
+
return;
|
|
46
|
+
hooked = true;
|
|
47
|
+
const self = import.meta.url;
|
|
48
|
+
register(new URL(self.endsWith('.ts') ? './resolve.ts' : './resolve.js', self), { parentURL: self, data: { parent: self } });
|
|
49
|
+
}
|
|
38
50
|
// The class source is a module. Every export that is a class is a class the
|
|
39
51
|
// harbor holds, under its export name. The harbor never sees a body: it
|
|
40
52
|
// constructs when a ward names a class, and never chooses one.
|
|
41
53
|
async function loadClasses(path) {
|
|
42
54
|
if (!existsSync(path))
|
|
43
55
|
return {};
|
|
56
|
+
hook();
|
|
44
57
|
// Keyed by the file's own time, so a source that changed on disk is read
|
|
45
58
|
// again and a harbor boots on what it pins, not on what it once loaded.
|
|
46
59
|
const { mtimeMs } = await stat(path);
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
type Context = {
|
|
2
|
+
parentURL?: string;
|
|
3
|
+
conditions: string[];
|
|
4
|
+
importAttributes: Record<string, string>;
|
|
5
|
+
};
|
|
6
|
+
type Resolved = {
|
|
7
|
+
url: string;
|
|
8
|
+
format?: string | null;
|
|
9
|
+
shortCircuit?: boolean;
|
|
10
|
+
};
|
|
11
|
+
type Next = (specifier: string, context: Context) => Promise<Resolved>;
|
|
12
|
+
export declare function initialize(data: {
|
|
13
|
+
parent: string;
|
|
14
|
+
}): void;
|
|
15
|
+
export declare function resolve(specifier: string, context: Context, next: Next): Promise<Resolved>;
|
|
16
|
+
export {};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
let parent = '';
|
|
2
|
+
export function initialize(data) {
|
|
3
|
+
parent = data.parent;
|
|
4
|
+
}
|
|
5
|
+
const bare = (s) => !s.startsWith('.') && !s.startsWith('/') && !/^[a-z]+:/i.test(s);
|
|
6
|
+
export async function resolve(specifier, context, next) {
|
|
7
|
+
try {
|
|
8
|
+
return await next(specifier, context);
|
|
9
|
+
}
|
|
10
|
+
catch (e) {
|
|
11
|
+
const missing = e.code === 'ERR_MODULE_NOT_FOUND';
|
|
12
|
+
const from = context.parentURL ?? '';
|
|
13
|
+
if (missing && parent && bare(specifier) && from.startsWith('file:') && !from.includes('/node_modules/'))
|
|
14
|
+
return next(specifier, { ...context, parentURL: parent });
|
|
15
|
+
throw e;
|
|
16
|
+
}
|
|
17
|
+
}
|
package/harbor/disk.ts
CHANGED
|
@@ -17,6 +17,7 @@ import { mkdir, readFile, writeFile, unlink, stat } from 'node:fs/promises';
|
|
|
17
17
|
import { existsSync } from 'node:fs';
|
|
18
18
|
import { join, resolve, isAbsolute } from 'node:path';
|
|
19
19
|
import { pathToFileURL } from 'node:url';
|
|
20
|
+
import { register } from 'node:module';
|
|
20
21
|
import process from 'node:process';
|
|
21
22
|
import type { BeingClass } from '@quo-systems/quo';
|
|
22
23
|
import { User, Desk, Avatar } from '../beings/index.ts';
|
|
@@ -33,11 +34,23 @@ export const BUILT_IN: Record<string, BeingClass> = { User, Desk, Avatar };
|
|
|
33
34
|
// second ward with one pk.
|
|
34
35
|
const HELD = new Set<string>();
|
|
35
36
|
|
|
37
|
+
// A class file in the harbor folder resolves its packages from where the
|
|
38
|
+
// dock is installed, `resolve.ts`: the folder has no node_modules and is not
|
|
39
|
+
// meant to. Registered once per process, before the first class loads.
|
|
40
|
+
let hooked = false;
|
|
41
|
+
function hook(): void {
|
|
42
|
+
if (hooked) return;
|
|
43
|
+
hooked = true;
|
|
44
|
+
const self = import.meta.url;
|
|
45
|
+
register(new URL(self.endsWith('.ts') ? './resolve.ts' : './resolve.js', self), { parentURL: self, data: { parent: self } });
|
|
46
|
+
}
|
|
47
|
+
|
|
36
48
|
// The class source is a module. Every export that is a class is a class the
|
|
37
49
|
// harbor holds, under its export name. The harbor never sees a body: it
|
|
38
50
|
// constructs when a ward names a class, and never chooses one.
|
|
39
51
|
async function loadClasses(path: string): Promise<Record<string, BeingClass>> {
|
|
40
52
|
if (!existsSync(path)) return {};
|
|
53
|
+
hook();
|
|
41
54
|
// Keyed by the file's own time, so a source that changed on disk is read
|
|
42
55
|
// again and a harbor boots on what it pins, not on what it once loaded.
|
|
43
56
|
const { mtimeMs } = await stat(path);
|
package/harbor/quo-harbor.md
CHANGED
|
@@ -339,6 +339,15 @@ core plus files, the loader and the pid, and holds the `ws` listener's end of
|
|
|
339
339
|
every socket dialed to it; the browser harbor, `browser.ts`, is the core plus
|
|
340
340
|
IndexedDB, the built-in beings, the lock and one dialer per world.
|
|
341
341
|
|
|
342
|
+
The harbor folder is the device's and not a package: it has no node_modules
|
|
343
|
+
and is not meant to, and a class file in it imports the library by name like
|
|
344
|
+
any code. So the disk harbor resolves a class file's packages from where the
|
|
345
|
+
dock itself is installed, `resolve.ts`, a module resolution hook registered
|
|
346
|
+
once before the first class loads: a bare name that does not resolve from a
|
|
347
|
+
file outside any node_modules is resolved again from the dock's own place.
|
|
348
|
+
A class sees the packages the daemon sees, the library and the dock's own,
|
|
349
|
+
and one copy of each, so a `Being` in a class file is the daemon's `Being`.
|
|
350
|
+
|
|
342
351
|
A tab's store keeps values through JSON, as a file does, because the ward
|
|
343
352
|
hands its partition out through a guard that structured clone refuses.
|
|
344
353
|
The daemon's `/quo` and `/health` answer any origin, with the preflight a
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
// How a class file the harbor folder holds finds its packages. The folder
|
|
3
|
+
// is the device's, not a package: it has no node_modules, and a class in it
|
|
4
|
+
// that imports `@quo-systems/quo` would otherwise fail to resolve from where
|
|
5
|
+
// it sits. This is a module resolution hook, registered once by the disk
|
|
6
|
+
// harbor: a bare specifier that does not resolve from a file outside any
|
|
7
|
+
// node_modules is resolved again from where the dock itself is installed,
|
|
8
|
+
// so a class file sees exactly the packages the daemon sees, the library
|
|
9
|
+
// and the dock's own, and one copy of each.
|
|
10
|
+
type Context = { parentURL?: string; conditions: string[]; importAttributes: Record<string, string> };
|
|
11
|
+
type Resolved = { url: string; format?: string | null; shortCircuit?: boolean };
|
|
12
|
+
type Next = (specifier: string, context: Context) => Promise<Resolved>;
|
|
13
|
+
|
|
14
|
+
let parent = '';
|
|
15
|
+
|
|
16
|
+
export function initialize(data: { parent: string }): void {
|
|
17
|
+
parent = data.parent;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const bare = (s: string) => !s.startsWith('.') && !s.startsWith('/') && !/^[a-z]+:/i.test(s);
|
|
21
|
+
|
|
22
|
+
export async function resolve(specifier: string, context: Context, next: Next): Promise<Resolved> {
|
|
23
|
+
try {
|
|
24
|
+
return await next(specifier, context);
|
|
25
|
+
} catch (e) {
|
|
26
|
+
const missing = (e as { code?: string }).code === 'ERR_MODULE_NOT_FOUND';
|
|
27
|
+
const from = context.parentURL ?? '';
|
|
28
|
+
if (missing && parent && bare(specifier) && from.startsWith('file:') && !from.includes('/node_modules/')) return next(specifier, { ...context, parentURL: parent });
|
|
29
|
+
throw e;
|
|
30
|
+
}
|
|
31
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@quo-systems/dock",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.6",
|
|
4
4
|
"description": "The dock: what every estate on Quo needs and nobody writes twice. A daemon and the quo command, the front desk, the user being and the avatar, harbors on disk, in a tab and on the edge, the model sides and the screen.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"quo",
|
|
@@ -72,7 +72,7 @@
|
|
|
72
72
|
"build": "rm -rf dist && tsc -p tsconfig.build.json && cp harbor/edge/platform.d.ts dist/harbor/edge/ && cp -R cli/estate dist/cli/",
|
|
73
73
|
"test": "node --test \"test/*.test.ts\"",
|
|
74
74
|
"check:terrain": "node --test \"test/terrain/*.test.ts\"",
|
|
75
|
-
"prepublishOnly": "
|
|
75
|
+
"prepublishOnly": "test \"$QUO_GATED\" = 1 || { echo 'publish from the root, gated once: npm run release' >&2; exit 1; }"
|
|
76
76
|
},
|
|
77
77
|
"dependencies": {
|
|
78
78
|
"@aparajita/capacitor-secure-storage": "^8.0.0",
|
|
@@ -80,7 +80,7 @@
|
|
|
80
80
|
"@capacitor/core": "^8.5.1",
|
|
81
81
|
"@capacitor/filesystem": "^8.1.3",
|
|
82
82
|
"@modelcontextprotocol/sdk": "^1.30.0",
|
|
83
|
-
"@quo-systems/quo": "^0.2.
|
|
83
|
+
"@quo-systems/quo": "^0.2.6",
|
|
84
84
|
"esbuild": "^0.28.2",
|
|
85
85
|
"ws": "^8.21.3"
|
|
86
86
|
},
|