holosphere 1.3.0-alpha4 → 1.3.0-alpha7
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/content.js +219 -46
- package/federation.js +47 -13
- package/global.js +49 -4
- package/hologram.js +21 -4
- package/holosphere-bundle.esm.js +532 -283
- package/holosphere-bundle.js +532 -283
- package/holosphere-bundle.min.js +8 -8
- package/holosphere.d.ts +96 -9
- package/holosphere.js +53 -8
- package/package.json +1 -1
- package/utils.js +29 -7
package/holosphere.d.ts
CHANGED
|
@@ -15,11 +15,39 @@ interface PutOptions {
|
|
|
15
15
|
disableHologramRedirection?: boolean;
|
|
16
16
|
actingAs?: string;
|
|
17
17
|
password?: string | null;
|
|
18
|
+
/**
|
|
19
|
+
* Per-put deadline (ms) for Gun's ack callback. When the deadline fires,
|
|
20
|
+
* the returned promise resolves with `{ success: true, queued: true, ... }`
|
|
21
|
+
* so an offline/partitioned mesh can't hang the caller — Gun keeps the
|
|
22
|
+
* write locally and replays it on reconnect. Default 5000. Pass `0` to
|
|
23
|
+
* disable and wait for ack indefinitely.
|
|
24
|
+
*/
|
|
25
|
+
timeout?: number;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
interface PutGlobalOptions {
|
|
29
|
+
/**
|
|
30
|
+
* Per-put deadline (ms) for Gun's ack callback. The promise still
|
|
31
|
+
* resolves to `undefined` (its public contract); on timeout a warning
|
|
32
|
+
* is logged. Default 5000. Pass `0` to disable.
|
|
33
|
+
*/
|
|
34
|
+
timeout?: number;
|
|
18
35
|
}
|
|
19
36
|
|
|
20
37
|
interface GetOptions {
|
|
21
38
|
resolveHolograms?: boolean;
|
|
22
39
|
validationOptions?: object;
|
|
40
|
+
/** Per-`.once()` deadline in ms; cold paths resolve `null` after this. Default 8000. Pass `0` to disable. */
|
|
41
|
+
timeout?: number;
|
|
42
|
+
/** Return `_deleted: true` soft-tombstoned records instead of treating them as not-found. Default false. */
|
|
43
|
+
includeDeleted?: boolean;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
interface GetAllOptions {
|
|
47
|
+
/** Per-`.once()` deadline in ms; cold paths resolve `[]` after this. Default 8000. Pass `0` to disable. */
|
|
48
|
+
timeout?: number;
|
|
49
|
+
/** Include `_deleted: true` soft-tombstoned records in the response. Default false. */
|
|
50
|
+
includeDeleted?: boolean;
|
|
23
51
|
}
|
|
24
52
|
|
|
25
53
|
interface ResolveHologramOptions {
|
|
@@ -42,22 +70,69 @@ interface Hologram {
|
|
|
42
70
|
*
|
|
43
71
|
* On success: `isHologram === true` and source* fields point at the origin.
|
|
44
72
|
* On failure: `isHologram === false` and `error` describes why resolution failed.
|
|
73
|
+
*
|
|
74
|
+
* **Exported.** Domain types in consumers should declare
|
|
75
|
+
* `_hologram?: ResolvedHologramMeta` instead of inlining the shape.
|
|
45
76
|
*/
|
|
46
|
-
interface ResolvedHologramMeta {
|
|
77
|
+
export interface ResolvedHologramMeta {
|
|
47
78
|
isHologram: boolean;
|
|
48
79
|
soul: string;
|
|
49
80
|
sourceHolon?: string | null;
|
|
50
81
|
sourceLens?: string | null;
|
|
51
82
|
sourceKey?: string | null;
|
|
83
|
+
/** Display name of the source holon — stamped by `resolveHologram` when known. */
|
|
84
|
+
sourceHolonName?: string;
|
|
52
85
|
resolvedAt: number;
|
|
53
86
|
error?: string;
|
|
54
87
|
}
|
|
55
88
|
|
|
56
|
-
interface ResolvedHologramData {
|
|
89
|
+
export interface ResolvedHologramData {
|
|
57
90
|
_hologram: ResolvedHologramMeta;
|
|
58
91
|
[key: string]: any;
|
|
59
92
|
}
|
|
60
93
|
|
|
94
|
+
/**
|
|
95
|
+
* Federation provenance envelope stamped on records that arrived via a
|
|
96
|
+
* federated partner. Set by `getFederated` and by `propagate` when writing
|
|
97
|
+
* to outbound partners.
|
|
98
|
+
*
|
|
99
|
+
* **Exported.** Domain types should declare `_federation?: FederationMeta`
|
|
100
|
+
* instead of inlining the shape.
|
|
101
|
+
*/
|
|
102
|
+
export interface FederationMeta {
|
|
103
|
+
/** The holon the record was propagated FROM. */
|
|
104
|
+
origin: string;
|
|
105
|
+
/** The lens it was published under at the origin. */
|
|
106
|
+
sourceLens: string;
|
|
107
|
+
/** Origin holon's display name (best-effort; absent if the source has no name). */
|
|
108
|
+
originName?: string;
|
|
109
|
+
/** Source-side id of the record, useful when local-side keys differ. */
|
|
110
|
+
originalId?: string;
|
|
111
|
+
/** Wall-clock ms when the propagation was emitted. */
|
|
112
|
+
propagatedAt?: number;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Soft-tombstone marker recognised by `get`/`getAll`. A record with
|
|
117
|
+
* `_deleted: true` is treated as not-found in the default response and
|
|
118
|
+
* surfaced only when `{ includeDeleted: true }` is passed.
|
|
119
|
+
*
|
|
120
|
+
* **Exported.** Domain types that want to allow tombstoned records on the
|
|
121
|
+
* wire should declare `_deleted?: boolean`.
|
|
122
|
+
*/
|
|
123
|
+
export type DeletedMarker = boolean;
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Convenience mixin: the three envelope fields the library stamps onto
|
|
127
|
+
* records. Domain types can extend or intersect with this to avoid
|
|
128
|
+
* redeclaring the shapes.
|
|
129
|
+
*/
|
|
130
|
+
export interface HolosphereEnvelope {
|
|
131
|
+
_hologram?: ResolvedHologramMeta;
|
|
132
|
+
_federation?: FederationMeta;
|
|
133
|
+
_deleted?: DeletedMarker;
|
|
134
|
+
}
|
|
135
|
+
|
|
61
136
|
/**
|
|
62
137
|
* Per-partner directional lens config. Directions are from the holding
|
|
63
138
|
* space's perspective: `inbound` lenses are received from the partner,
|
|
@@ -123,6 +198,16 @@ interface PutResult {
|
|
|
123
198
|
pathKey: string;
|
|
124
199
|
propagationResult?: PropagationResult | null;
|
|
125
200
|
error?: string;
|
|
201
|
+
/**
|
|
202
|
+
* `true` when the ack deadline fired before Gun confirmed the write
|
|
203
|
+
* (offline/partitioned mesh). The write is still committed locally
|
|
204
|
+
* via radisk and Gun replays it whenever a peer reappears; subscriber
|
|
205
|
+
* notification, hologram cascade, and federation propagation run at
|
|
206
|
+
* that point. Absent or `false` when the put was acknowledged.
|
|
207
|
+
*/
|
|
208
|
+
queued?: boolean;
|
|
209
|
+
/** Holograms whose `updated` timestamp was bumped by this put (empty when the put timed out). */
|
|
210
|
+
updatedHolograms?: Array<{ soul: string; holon: string; lens: string; key: string }>;
|
|
126
211
|
}
|
|
127
212
|
|
|
128
213
|
interface CanWriteResult {
|
|
@@ -168,7 +253,7 @@ declare class HoloSphere {
|
|
|
168
253
|
put(holon: string, lens: string, data: object, password?: string | null, options?: PutOptions): Promise<PutResult>;
|
|
169
254
|
get(holon: string, lens: string): Promise<any | null>;
|
|
170
255
|
get(holon: string, lens: string, key: string, password?: string | null, options?: GetOptions): Promise<any | null>;
|
|
171
|
-
getAll(holon: string, lens: string, password?: string | null): Promise<Array<any>>;
|
|
256
|
+
getAll(holon: string, lens: string, password?: string | null, options?: GetAllOptions): Promise<Array<any>>;
|
|
172
257
|
parse(rawData: any): Promise<object | null>;
|
|
173
258
|
delete(holon: string, lens: string, key: string, password?: string | null): Promise<boolean>;
|
|
174
259
|
deleteAll(holon: string, lens: string, password?: string | null): Promise<boolean>;
|
|
@@ -181,14 +266,14 @@ declare class HoloSphere {
|
|
|
181
266
|
deleteNode(holon: string, lens: string, key: string): Promise<boolean>;
|
|
182
267
|
|
|
183
268
|
// Global
|
|
184
|
-
putGlobal(tableName: string, data: object, password?: string | null): Promise<void>;
|
|
185
|
-
writeGlobal(tableName: string, data: object): Promise<void>;
|
|
269
|
+
putGlobal(tableName: string, data: object, password?: string | null, options?: PutGlobalOptions): Promise<void>;
|
|
270
|
+
writeGlobal(tableName: string, data: object, options?: PutGlobalOptions): Promise<void>;
|
|
186
271
|
getGlobal(tableName: string, key: string, password?: string | null): Promise<any | null>;
|
|
187
272
|
getAllGlobal(tableName: string, password?: string | null): Promise<Array<any>>;
|
|
188
273
|
deleteGlobal(tableName: string, key: string, password?: string | null): Promise<boolean>;
|
|
189
274
|
deleteAllGlobal(tableName: string, password?: string | null): Promise<boolean>;
|
|
190
|
-
subscribeGlobal(lens: string, key: string | null, callback: (data: any, key?: string) => void, options?: { realtimeOnly?: boolean }):
|
|
191
|
-
subscribeGlobal(lens: string, callback: (data: any, key?: string) => void):
|
|
275
|
+
subscribeGlobal(lens: string, key: string | null, callback: (data: any, key?: string) => void, options?: { realtimeOnly?: boolean }): { unsubscribe: () => void };
|
|
276
|
+
subscribeGlobal(lens: string, callback: (data: any, key?: string) => void): { unsubscribe: () => void };
|
|
192
277
|
|
|
193
278
|
// Hologram
|
|
194
279
|
createHologram(holon: string, lens: string, data: { id: string, [key: string]: any }): Hologram;
|
|
@@ -210,8 +295,10 @@ declare class HoloSphere {
|
|
|
210
295
|
getScalespace(lat: number, lng: number): string[];
|
|
211
296
|
getHolonScalespace(holon: string): string[];
|
|
212
297
|
|
|
213
|
-
// Subscription
|
|
214
|
-
|
|
298
|
+
// Subscription. Returns synchronously — `await` on the return value
|
|
299
|
+
// still works (await on a non-Promise resolves to the value), so both
|
|
300
|
+
// call styles produce the same `{ unsubscribe }` shape.
|
|
301
|
+
subscribe(holon: string, lens: string, callback: (data: any, key?: string) => void): { unsubscribe: () => void };
|
|
215
302
|
|
|
216
303
|
// Federation - v1 style
|
|
217
304
|
federate(holonId1: string, holonId2: string, password1?: string | null, password2?: string | null, bidirectional?: boolean, lensConfig?: { inbound?: string[], outbound?: string[] }): Promise<boolean>;
|
package/holosphere.js
CHANGED
|
@@ -125,6 +125,10 @@ class HoloSphere {
|
|
|
125
125
|
// Initialize schema cache
|
|
126
126
|
this.schemaCache = new Map();
|
|
127
127
|
|
|
128
|
+
// Holon-name cache so resolveHologram + getFederated don't refetch
|
|
129
|
+
// `settings/<holon>` for every hologram from the same source.
|
|
130
|
+
this._holonNameCache = new Map();
|
|
131
|
+
|
|
128
132
|
// Initialize allowed authors set (for canWrite)
|
|
129
133
|
this._allowedAuthors = new Set();
|
|
130
134
|
}
|
|
@@ -192,8 +196,8 @@ class HoloSphere {
|
|
|
192
196
|
return ContentOps.get(this, holon, lens, key, password, options);
|
|
193
197
|
}
|
|
194
198
|
|
|
195
|
-
async getAll(holon, lens, password = null) {
|
|
196
|
-
return ContentOps.getAll(this, holon, lens, password);
|
|
199
|
+
async getAll(holon, lens, password = null, options = {}) {
|
|
200
|
+
return ContentOps.getAll(this, holon, lens, password, options);
|
|
197
201
|
}
|
|
198
202
|
|
|
199
203
|
async parse(rawData) {
|
|
@@ -232,15 +236,15 @@ class HoloSphere {
|
|
|
232
236
|
|
|
233
237
|
// ================================ GLOBAL FUNCTIONS ================================
|
|
234
238
|
|
|
235
|
-
async putGlobal(tableName, data, password = null) {
|
|
236
|
-
return GlobalOps.putGlobal(this, tableName, data, password);
|
|
239
|
+
async putGlobal(tableName, data, password = null, options = {}) {
|
|
240
|
+
return GlobalOps.putGlobal(this, tableName, data, password, options);
|
|
237
241
|
}
|
|
238
242
|
|
|
239
243
|
/**
|
|
240
244
|
* v2-compatible alias for putGlobal (no password param)
|
|
241
245
|
*/
|
|
242
|
-
async writeGlobal(tableName, data) {
|
|
243
|
-
return GlobalOps.putGlobal(this, tableName, data, null);
|
|
246
|
+
async writeGlobal(tableName, data, options = {}) {
|
|
247
|
+
return GlobalOps.putGlobal(this, tableName, data, null, options);
|
|
244
248
|
}
|
|
245
249
|
|
|
246
250
|
async getGlobal(tableName, key, password = null) {
|
|
@@ -262,8 +266,10 @@ class HoloSphere {
|
|
|
262
266
|
/**
|
|
263
267
|
* Subscribe to real-time changes in a global table.
|
|
264
268
|
* v2-compatible: subscribeGlobal(lens, key, callback, options)
|
|
269
|
+
*
|
|
270
|
+
* Returns synchronously — see {@link subscribe}.
|
|
265
271
|
*/
|
|
266
|
-
|
|
272
|
+
subscribeGlobal(lens, keyOrCallback, callbackOrOptions, options = {}) {
|
|
267
273
|
let key, callback;
|
|
268
274
|
if (typeof keyOrCallback === 'function') {
|
|
269
275
|
callback = keyOrCallback;
|
|
@@ -336,7 +342,14 @@ class HoloSphere {
|
|
|
336
342
|
return Utils.getHolonScalespace(holon);
|
|
337
343
|
}
|
|
338
344
|
|
|
339
|
-
|
|
345
|
+
/**
|
|
346
|
+
* Subscribe to real-time changes for a holon/lens.
|
|
347
|
+
*
|
|
348
|
+
* Synchronous return: `{ unsubscribe: () => void }`. Callers do not
|
|
349
|
+
* need to `await` — both `const s = holosphere.subscribe(...)` and
|
|
350
|
+
* `const s = await holosphere.subscribe(...)` yield the same shape.
|
|
351
|
+
*/
|
|
352
|
+
subscribe(holon, lens, callback) {
|
|
340
353
|
return Utils.subscribe(this, holon, lens, callback);
|
|
341
354
|
}
|
|
342
355
|
|
|
@@ -344,6 +357,38 @@ class HoloSphere {
|
|
|
344
357
|
return Utils.notifySubscribers(this, data);
|
|
345
358
|
}
|
|
346
359
|
|
|
360
|
+
/**
|
|
361
|
+
* Resolve a holon's display name from its `settings/<holon>` record.
|
|
362
|
+
* Cached per-instance so repeated lookups (e.g. one per hologram in a
|
|
363
|
+
* federated batch) only fetch once. Returns `null` when no name is set.
|
|
364
|
+
*
|
|
365
|
+
* Used internally by `resolveHologram` to stamp
|
|
366
|
+
* `_hologram.sourceHolonName` so every consumer of a resolved hologram
|
|
367
|
+
* already has the display name without a second round-trip.
|
|
368
|
+
*/
|
|
369
|
+
async getHolonName(holonId) {
|
|
370
|
+
if (!holonId) return null;
|
|
371
|
+
const key = String(holonId);
|
|
372
|
+
if (this._holonNameCache.has(key)) return this._holonNameCache.get(key);
|
|
373
|
+
try {
|
|
374
|
+
const settings = await this.get(key, 'settings', key);
|
|
375
|
+
let name = null;
|
|
376
|
+
if (settings) {
|
|
377
|
+
if (Array.isArray(settings)) {
|
|
378
|
+
const found = settings.find(s => s && typeof s.name === 'string' && s.name.trim() !== '');
|
|
379
|
+
name = found ? found.name : null;
|
|
380
|
+
} else if (typeof settings.name === 'string' && settings.name.trim() !== '') {
|
|
381
|
+
name = settings.name;
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
this._holonNameCache.set(key, name);
|
|
385
|
+
return name;
|
|
386
|
+
} catch {
|
|
387
|
+
this._holonNameCache.set(key, null);
|
|
388
|
+
return null;
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
|
|
347
392
|
generateId() {
|
|
348
393
|
return Utils.generateId();
|
|
349
394
|
}
|
package/package.json
CHANGED
package/utils.js
CHANGED
|
@@ -44,13 +44,21 @@ export function getHolonScalespace(holon) { // Doesn't need holoInstance
|
|
|
44
44
|
|
|
45
45
|
/**
|
|
46
46
|
* Subscribes to changes in a specific holon and lens.
|
|
47
|
+
*
|
|
48
|
+
* Returns **synchronously** — the call has no internal awaits, so callers
|
|
49
|
+
* never need to `await` the result. (`await` on the return value still
|
|
50
|
+
* works as before; it just resolves through the value unchanged.) This
|
|
51
|
+
* means `const sub = holosphere.subscribe(...); sub.unsubscribe();` is the
|
|
52
|
+
* canonical pattern and there is no `Promise<{ unsubscribe }>` shape to
|
|
53
|
+
* disambiguate against the resolved object.
|
|
54
|
+
*
|
|
47
55
|
* @param {HoloSphere} holoInstance - The HoloSphere instance.
|
|
48
56
|
* @param {string} holon - The holon identifier.
|
|
49
57
|
* @param {string} lens - The lens to subscribe to.
|
|
50
58
|
* @param {function} callback - The callback to execute on changes.
|
|
51
|
-
* @returns {
|
|
59
|
+
* @returns {{ unsubscribe: () => void }} - Subscription with unsubscribe method.
|
|
52
60
|
*/
|
|
53
|
-
export
|
|
61
|
+
export function subscribe(holoInstance, holon, lens, callback) {
|
|
54
62
|
if (!holon || !lens) {
|
|
55
63
|
throw new Error('subscribe: Missing holon or lens parameters:', holon, lens);
|
|
56
64
|
}
|
|
@@ -82,6 +90,17 @@ export async function subscribe(holoInstance, holon, lens, callback) {
|
|
|
82
90
|
}
|
|
83
91
|
}
|
|
84
92
|
|
|
93
|
+
// Subscribers expect `object | null`. `parse()` can return
|
|
94
|
+
// a string/number/boolean when a legacy or corrupted leaf
|
|
95
|
+
// happened to be a JSON-encoded primitive (e.g.
|
|
96
|
+
// `'"hello"'` parses to `'hello'`). Drop those so every
|
|
97
|
+
// consumer can stop guarding with
|
|
98
|
+
// `typeof x === 'string' ? JSON.parse(x) : x`.
|
|
99
|
+
if (parsed !== null && (typeof parsed !== 'object' || Array.isArray(parsed))) {
|
|
100
|
+
console.warn(`[holosphere.subscribe] dropping non-object payload at ${holon}/${lens}/${key}:`, typeof parsed);
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
|
|
85
104
|
// Check again if subscription ID still exists before calling callback
|
|
86
105
|
if (holoInstance.subscriptions[subscriptionId]) {
|
|
87
106
|
callback(parsed, key);
|
|
@@ -102,9 +121,14 @@ export async function subscribe(holoInstance, holon, lens, callback) {
|
|
|
102
121
|
gunListener: gunListener // Store the listener too (optional, maybe needed for close?)
|
|
103
122
|
};
|
|
104
123
|
|
|
105
|
-
// Return an object with unsubscribe method
|
|
124
|
+
// Return an object with unsubscribe method.
|
|
125
|
+
// `unsubscribe` is sync — nothing it does (`mapChain.off()`,
|
|
126
|
+
// `delete subscriptions[id]`) blocks. Keeping it sync means the
|
|
127
|
+
// returned shape is `{ unsubscribe: () => void }` rather than
|
|
128
|
+
// `() => Promise<void>`, matching what callers expect when they
|
|
129
|
+
// store it in a `() => void` cleanup slot.
|
|
106
130
|
return {
|
|
107
|
-
unsubscribe:
|
|
131
|
+
unsubscribe: () => {
|
|
108
132
|
const sub = holoInstance.subscriptions[subscriptionId];
|
|
109
133
|
if (!sub) {
|
|
110
134
|
return;
|
|
@@ -114,9 +138,7 @@ export async function subscribe(holoInstance, holon, lens, callback) {
|
|
|
114
138
|
// Turn off the Gun subscription using the stored mapChain reference
|
|
115
139
|
if (sub.mapChain) { // Check if mapChain exists
|
|
116
140
|
sub.mapChain.off(); // Call off() on the chain where .on() was attached
|
|
117
|
-
|
|
118
|
-
// await new Promise(res => setTimeout(res, 50));
|
|
119
|
-
} // We might not need to call off() on gunListener explicitly
|
|
141
|
+
}
|
|
120
142
|
|
|
121
143
|
// Remove from subscriptions object AFTER turning off listener
|
|
122
144
|
delete holoInstance.subscriptions[subscriptionId];
|