@xnetjs/data 1.0.0 → 2.0.0
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/auth/index.d.ts +4 -4
- package/dist/checkpoint-MZHJ7PCZ.js +12 -0
- package/dist/{chunk-CARPMPXV.js → chunk-4S73XDWH.js} +318 -19
- package/dist/chunk-6ZUJID52.js +41 -0
- package/dist/{chunk-ENA6NEFY.js → chunk-DESSPQBS.js} +54 -5
- package/dist/chunk-MEAIGK3W.js +58 -0
- package/dist/{chunk-5B47M2IG.js → chunk-U223J425.js} +10 -6
- package/dist/chunk-Z5I2T75Z.js +70 -0
- package/dist/{clone-Bj0nQyMc.d.ts → clone-DNcpkYAA.d.ts} +2 -2
- package/dist/database/index.d.ts +3 -3
- package/dist/database/index.js +2 -2
- package/dist/debug-report-BTNZAKX3.js +10 -0
- package/dist/draft-KGHITKX2.js +10 -0
- package/dist/{grant-expiration-cleaner-BrMW0ffX.d.ts → grant-expiration-cleaner-1by9wRlb.d.ts} +2 -2
- package/dist/index.d.ts +7 -7
- package/dist/index.js +431 -415
- package/dist/{query-ast-ESRn8U2H.d.ts → query-ast-NV5StnHo.d.ts} +1 -1
- package/dist/{registry-BGD80QCd.d.ts → registry-s1fYgCSj.d.ts} +1 -1
- package/dist/schema/index.d.ts +173 -12
- package/dist/schema/index.js +73 -57
- package/dist/store/index.d.ts +9 -5
- package/dist/store/index.js +2 -2
- package/dist/{store-B0fb6WdJ.d.ts → store-BIDKtpjW.d.ts} +56 -1
- package/dist/{types-B5rrBwRI.d.ts → types-C64g-IXg.d.ts} +70 -2
- package/package.json +7 -7
package/dist/auth/index.d.ts
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { AllowExpr, DenyExpr, AuthExpression, AndExpr, OrExpr, NotExpr, PublicExpr, AuthenticatedExpr, CreatorRoleResolver, PropertyRoleResolver, RelationRoleResolver, MembershipRoleResolver, AuthorizationDefinition, SerializedAuthorization, SerializedAuthExpression, DID } from '@xnetjs/core';
|
|
2
2
|
export { AUTH_ACTIONS, AllowExpr, AndExpr, AuthAction, AuthCheckInput, AuthDecision, AuthDenyReason, AuthExpression, AuthTrace, AuthTraceStep, AuthenticatedExpr, AuthorizationDefinition, CreatorRoleResolver, DenyExpr, MembershipRoleResolver, NotExpr, OrExpr, PolicyEvaluator, PropertyRoleResolver, PublicExpr, RelationRoleResolver, RoleRefExpr, RoleResolver, SerializedAuthExpression, SerializedAuthorization, SerializedRoleResolver } from '@xnetjs/core';
|
|
3
|
-
import {
|
|
4
|
-
export {
|
|
5
|
-
export { A as ActionPermission, m as AuthDecisionEvent, b as DecisionCache, g as DecisionCacheOptions, D as DefaultPolicyEvaluator, j as DefaultPolicyEvaluatorOptions, a as DefaultRoleResolver, i as DefaultRoleResolverOptions, k as DefinedSchemaLike, G as GrantExpirationCleaner, o as GrantExpirationCleanerOptions, n as GrantExpirationStore, H as HubPolicy, N as NodeStoreReader, P as PermissionMatrix, l as RevalidationEvent, R as RoleSummary, S as SchemaRegistryReader, c as buildPermissionMatrix, f as createPolicyEvaluator, d as describeRoleResolver, e as evaluateExpression, h as hubActionsForSpaceRole, s as schemaToHubPolicy } from '../grant-expiration-cleaner-
|
|
3
|
+
import { F as PropertyDefinition, a as Schema, h as NodeState, S as SchemaIRI } from '../types-C64g-IXg.js';
|
|
4
|
+
export { a_ as DEFAULT_OFFLINE_POLICY, aY as GRANT_SCHEMA_IRI, b4 as Grant, x as GrantIndex, b9 as GrantIndexOptions, b8 as GrantIndexStore, b3 as GrantInput, b7 as GrantNode, aX as GrantRateLimiter, bc as GrantRateLimiterOptions, O as OfflineAuthPolicy, bb as RevocationConfig, ba as RevocationConsistency, aW as StoreAuth, e as StoreAuthAPI, b5 as StoreAuthError, b6 as StoreAuthErrorCode, b2 as StoreAuthKeyManager, b0 as StoreAuthOptions, b1 as StoreAuthStore, aZ as isGrantActive, a$ as mergeOfflinePolicy } from '../types-C64g-IXg.js';
|
|
5
|
+
export { A as ActionPermission, m as AuthDecisionEvent, b as DecisionCache, g as DecisionCacheOptions, D as DefaultPolicyEvaluator, j as DefaultPolicyEvaluatorOptions, a as DefaultRoleResolver, i as DefaultRoleResolverOptions, k as DefinedSchemaLike, G as GrantExpirationCleaner, o as GrantExpirationCleanerOptions, n as GrantExpirationStore, H as HubPolicy, N as NodeStoreReader, P as PermissionMatrix, l as RevalidationEvent, R as RoleSummary, S as SchemaRegistryReader, c as buildPermissionMatrix, f as createPolicyEvaluator, d as describeRoleResolver, e as evaluateExpression, h as hubActionsForSpaceRole, s as schemaToHubPolicy } from '../grant-expiration-cleaner-1by9wRlb.js';
|
|
6
6
|
import { PublicKeyResolver } from '@xnetjs/crypto';
|
|
7
7
|
import '@xnetjs/sqlite';
|
|
8
8
|
import '@xnetjs/sync';
|
|
9
|
-
import '../registry-
|
|
9
|
+
import '../registry-s1fYgCSj.js';
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
12
|
* Authorization expression builders for schema definitions.
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CHECKPOINT_SCHEMA_IRI,
|
|
3
|
+
CheckpointSchema
|
|
4
|
+
} from "./chunk-6ZUJID52.js";
|
|
5
|
+
import "./chunk-JA56EQJO.js";
|
|
6
|
+
import "./chunk-LK5ZIAIE.js";
|
|
7
|
+
import "./chunk-RRXJNZX5.js";
|
|
8
|
+
import "./chunk-D4LUUZYD.js";
|
|
9
|
+
export {
|
|
10
|
+
CHECKPOINT_SCHEMA_IRI,
|
|
11
|
+
CheckpointSchema
|
|
12
|
+
};
|
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
import {
|
|
2
|
-
SYSTEM_SCHEMA_BASE_IRIS,
|
|
3
|
-
isSystemNamespaceResource,
|
|
4
|
-
isSystemSchemaIri
|
|
5
|
-
} from "./chunk-XJBDESCX.js";
|
|
6
1
|
import {
|
|
7
2
|
applyNodeQueryDescriptor,
|
|
8
3
|
getNodeQuerySearchTokens,
|
|
9
4
|
withoutNodeQueryMaterializedView,
|
|
10
5
|
withoutNodeQueryPagination
|
|
11
6
|
} from "./chunk-S5RP5RKY.js";
|
|
7
|
+
import {
|
|
8
|
+
SYSTEM_SCHEMA_BASE_IRIS,
|
|
9
|
+
isSystemNamespaceResource,
|
|
10
|
+
isSystemSchemaIri
|
|
11
|
+
} from "./chunk-XJBDESCX.js";
|
|
12
12
|
import {
|
|
13
13
|
createNodeId,
|
|
14
14
|
getBaseSchemaIRI
|
|
@@ -1393,6 +1393,14 @@ var NodeStore = class {
|
|
|
1393
1393
|
listeners = /* @__PURE__ */ new Set();
|
|
1394
1394
|
nodeListeners = /* @__PURE__ */ new Map();
|
|
1395
1395
|
batchListeners = /* @__PURE__ */ new Set();
|
|
1396
|
+
// Draft overlay (exploration 0329)
|
|
1397
|
+
checkedOutDraft = null;
|
|
1398
|
+
cloneToOriginal = /* @__PURE__ */ new Map();
|
|
1399
|
+
draftMemberSet = /* @__PURE__ */ new Set();
|
|
1400
|
+
draftDynamicMembers = false;
|
|
1401
|
+
/** null = conservative "all schemas" (memberSchemaIds omitted). */
|
|
1402
|
+
draftMemberSchemaSet = null;
|
|
1403
|
+
draftOverlayListeners = /* @__PURE__ */ new Set();
|
|
1396
1404
|
schemaLookup;
|
|
1397
1405
|
propertyLookup;
|
|
1398
1406
|
lensRegistry;
|
|
@@ -1472,6 +1480,7 @@ var NodeStore = class {
|
|
|
1472
1480
|
});
|
|
1473
1481
|
this.emit(change2, node2, null, false);
|
|
1474
1482
|
this.authEvaluator?.invalidate(node2.id);
|
|
1483
|
+
this.notifyDraftNodeCreated(node2.id, node2.schemaId);
|
|
1475
1484
|
if (this.telemetry) {
|
|
1476
1485
|
this.telemetry.reportPerformance("data.create", Date.now() - start);
|
|
1477
1486
|
this.telemetry.reportUsage("data.create", 1);
|
|
@@ -1507,6 +1516,7 @@ var NodeStore = class {
|
|
|
1507
1516
|
await this.persistEncryptedNodeSnapshot(node);
|
|
1508
1517
|
this.emit(change, node, null, false);
|
|
1509
1518
|
this.authEvaluator?.invalidate(node.id);
|
|
1519
|
+
this.notifyDraftNodeCreated(node.id, node.schemaId);
|
|
1510
1520
|
if (this.telemetry) {
|
|
1511
1521
|
this.telemetry.reportPerformance("data.create", Date.now() - start);
|
|
1512
1522
|
this.telemetry.reportUsage("data.create", 1);
|
|
@@ -1523,10 +1533,186 @@ var NodeStore = class {
|
|
|
1523
1533
|
* Get a Node by ID.
|
|
1524
1534
|
*/
|
|
1525
1535
|
async get(id) {
|
|
1536
|
+
const cloneId = this.checkedOutDraft?.clones[id];
|
|
1537
|
+
if (cloneId) {
|
|
1538
|
+
const clone = await this.getRaw(cloneId);
|
|
1539
|
+
if (clone) return { ...clone, id };
|
|
1540
|
+
}
|
|
1541
|
+
return this.getRaw(id);
|
|
1542
|
+
}
|
|
1543
|
+
/**
|
|
1544
|
+
* Read a node WITHOUT the draft overlay (exploration 0329) — merge review
|
|
1545
|
+
* and diff surfaces use this to see main's true state while checked out.
|
|
1546
|
+
*/
|
|
1547
|
+
async getRaw(id) {
|
|
1526
1548
|
const node = await this.storage.getNode(id);
|
|
1527
1549
|
const decrypted = await this.decryptNodeSnapshotIfPresent(node);
|
|
1528
1550
|
return await this.canReadNode(decrypted) ? decrypted : null;
|
|
1529
1551
|
}
|
|
1552
|
+
// ==========================================================================
|
|
1553
|
+
// Draft overlay (exploration 0329)
|
|
1554
|
+
// ==========================================================================
|
|
1555
|
+
/**
|
|
1556
|
+
* Check a draft out (or pass null to return to main). While checked out,
|
|
1557
|
+
* member reads swap to clone content under original ids, member writes
|
|
1558
|
+
* redirect to clones (lazily forking via `onMissingMember`), and clone
|
|
1559
|
+
* change events mirror to original-id subscribers.
|
|
1560
|
+
*/
|
|
1561
|
+
setCheckedOutDraft(overlay) {
|
|
1562
|
+
this.checkedOutDraft = overlay;
|
|
1563
|
+
this.cloneToOriginal = new Map(
|
|
1564
|
+
overlay ? Object.entries(overlay.clones).map(([orig, clone]) => [clone, orig]) : []
|
|
1565
|
+
);
|
|
1566
|
+
this.draftMemberSet = overlay && overlay.members !== "dynamic" ? new Set(overlay.members) : /* @__PURE__ */ new Set();
|
|
1567
|
+
this.draftDynamicMembers = overlay?.members === "dynamic";
|
|
1568
|
+
this.draftMemberSchemaSet = overlay?.memberSchemaIds ? new Set(overlay.memberSchemaIds) : null;
|
|
1569
|
+
this.notifyDraftOverlayListeners();
|
|
1570
|
+
}
|
|
1571
|
+
/** Report a locally-created node to the checkout's draft-born hook (0329 P4). */
|
|
1572
|
+
notifyDraftNodeCreated(nodeId, schemaId) {
|
|
1573
|
+
const overlay = this.checkedOutDraft;
|
|
1574
|
+
if (!overlay?.onNodeCreated) return;
|
|
1575
|
+
if (nodeId === overlay.draftId || this.cloneToOriginal.has(nodeId)) return;
|
|
1576
|
+
try {
|
|
1577
|
+
overlay.onNodeCreated(nodeId, schemaId);
|
|
1578
|
+
} catch (err) {
|
|
1579
|
+
console.error("Error in draft onNodeCreated hook:", err);
|
|
1580
|
+
}
|
|
1581
|
+
}
|
|
1582
|
+
notifyDraftOverlayListeners() {
|
|
1583
|
+
for (const listener of this.draftOverlayListeners) {
|
|
1584
|
+
try {
|
|
1585
|
+
listener();
|
|
1586
|
+
} catch (err) {
|
|
1587
|
+
console.error("Error in draft overlay listener:", err);
|
|
1588
|
+
}
|
|
1589
|
+
}
|
|
1590
|
+
}
|
|
1591
|
+
getCheckedOutDraft() {
|
|
1592
|
+
return this.checkedOutDraft;
|
|
1593
|
+
}
|
|
1594
|
+
/**
|
|
1595
|
+
* Device-local draft privacy (exploration 0329): ids marked here (draft
|
|
1596
|
+
* bookkeeping nodes, clones, draft-born nodes) are excluded from outbound
|
|
1597
|
+
* sync publication by the personal node-sync room's `shouldPublish`
|
|
1598
|
+
* predicate. In-memory only — the draft module rehydrates it from open
|
|
1599
|
+
* Draft nodes at boot, BEFORE sync starts.
|
|
1600
|
+
*/
|
|
1601
|
+
draftPrivateNodeIds = /* @__PURE__ */ new Set();
|
|
1602
|
+
markDraftPrivate(ids) {
|
|
1603
|
+
for (const id of ids) this.draftPrivateNodeIds.add(id);
|
|
1604
|
+
}
|
|
1605
|
+
unmarkDraftPrivate(ids) {
|
|
1606
|
+
for (const id of ids) this.draftPrivateNodeIds.delete(id);
|
|
1607
|
+
}
|
|
1608
|
+
isDraftPrivate(id) {
|
|
1609
|
+
return this.draftPrivateNodeIds.has(id);
|
|
1610
|
+
}
|
|
1611
|
+
/** Notified on every checkout change (including clone-map refreshes). */
|
|
1612
|
+
subscribeToDraftOverlay(listener) {
|
|
1613
|
+
this.draftOverlayListeners.add(listener);
|
|
1614
|
+
return () => this.draftOverlayListeners.delete(listener);
|
|
1615
|
+
}
|
|
1616
|
+
/**
|
|
1617
|
+
* Swap checked-out members' content to their clones (original ids kept, so
|
|
1618
|
+
* MV id lists and grid ordering are untouched). No-op without a checkout.
|
|
1619
|
+
*/
|
|
1620
|
+
async overlayStates(states) {
|
|
1621
|
+
const overlay = this.checkedOutDraft;
|
|
1622
|
+
if (!overlay) return states;
|
|
1623
|
+
const visible = this.cloneToOriginal.size > 0 ? states.filter((state) => !this.cloneToOriginal.has(state.id)) : states;
|
|
1624
|
+
const cloneIds = visible.map((s) => overlay.clones[s.id]).filter((cloneId) => cloneId !== void 0);
|
|
1625
|
+
if (cloneIds.length === 0) return visible;
|
|
1626
|
+
const cloneMap = await this.getNodesById(cloneIds, this.storage);
|
|
1627
|
+
return Promise.all(
|
|
1628
|
+
visible.map(async (state) => {
|
|
1629
|
+
const cloneId = overlay.clones[state.id];
|
|
1630
|
+
const clone = cloneId ? cloneMap.get(cloneId) : void 0;
|
|
1631
|
+
if (!clone) return state;
|
|
1632
|
+
const decrypted = await this.decryptNodeSnapshotIfPresent(clone);
|
|
1633
|
+
return decrypted ? { ...decrypted, id: state.id } : state;
|
|
1634
|
+
})
|
|
1635
|
+
);
|
|
1636
|
+
}
|
|
1637
|
+
/**
|
|
1638
|
+
* Draft-aware query path (0329 P5): candidates from storage WITHOUT
|
|
1639
|
+
* pagination, unioned with checked-out members the predicate may have
|
|
1640
|
+
* missed (their clone values can change membership), content-swapped,
|
|
1641
|
+
* clone rows hidden, then the full descriptor re-applied in JS so
|
|
1642
|
+
* filter/sort/pagination reflect draft values.
|
|
1643
|
+
*/
|
|
1644
|
+
async draftAwareQuery(descriptor, start) {
|
|
1645
|
+
const overlay = this.checkedOutDraft;
|
|
1646
|
+
const unpaginated = withoutNodeQueryMaterializedView(withoutNodeQueryPagination(descriptor));
|
|
1647
|
+
let candidates;
|
|
1648
|
+
if (this.storage.queryNodes && !this.nodeContentCipher) {
|
|
1649
|
+
candidates = (await this.storage.queryNodes(unpaginated)).nodes;
|
|
1650
|
+
} else {
|
|
1651
|
+
const fallback = await this.loadQueryFallbackCandidates(unpaginated);
|
|
1652
|
+
const decrypted = await Promise.all(
|
|
1653
|
+
fallback.nodes.map((node) => this.decryptNodeSnapshotIfPresent(node))
|
|
1654
|
+
);
|
|
1655
|
+
candidates = decrypted.filter((node) => node !== null);
|
|
1656
|
+
}
|
|
1657
|
+
if (overlay) {
|
|
1658
|
+
const present = new Set(candidates.map((node) => node.id));
|
|
1659
|
+
const missing = Object.keys(overlay.clones).filter(
|
|
1660
|
+
(id) => !present.has(id)
|
|
1661
|
+
);
|
|
1662
|
+
if (missing.length > 0) {
|
|
1663
|
+
const extra = await this.getNodesById(missing, this.storage);
|
|
1664
|
+
for (const node of extra.values()) {
|
|
1665
|
+
const decrypted = await this.decryptNodeSnapshotIfPresent(node);
|
|
1666
|
+
if (!decrypted) continue;
|
|
1667
|
+
if (decrypted.schemaId !== descriptor.schemaId) continue;
|
|
1668
|
+
if (decrypted.deleted && !descriptor.includeDeleted) continue;
|
|
1669
|
+
candidates.push(decrypted);
|
|
1670
|
+
}
|
|
1671
|
+
}
|
|
1672
|
+
}
|
|
1673
|
+
const readable = await this.filterReadableNodes(candidates);
|
|
1674
|
+
const swapped = await this.overlayStates(readable);
|
|
1675
|
+
const filtered = applyNodeQueryDescriptor(swapped, withoutNodeQueryPagination(descriptor));
|
|
1676
|
+
const result = applyNodeQueryDescriptor(swapped, descriptor);
|
|
1677
|
+
return {
|
|
1678
|
+
nodes: result,
|
|
1679
|
+
totalCount: filtered.length,
|
|
1680
|
+
plan: {
|
|
1681
|
+
strategy: "draft-overlay",
|
|
1682
|
+
candidateNodeCount: readable.length,
|
|
1683
|
+
hydratedNodeCount: swapped.length,
|
|
1684
|
+
returnedNodeCount: result.length,
|
|
1685
|
+
durationMs: Date.now() - start
|
|
1686
|
+
}
|
|
1687
|
+
};
|
|
1688
|
+
}
|
|
1689
|
+
/**
|
|
1690
|
+
* Resolve where a write to `id` lands under the checkout: an existing
|
|
1691
|
+
* clone, a lazily-forked one (`onMissingMember`), or — for non-members,
|
|
1692
|
+
* clone ids, and declined forks — the id itself.
|
|
1693
|
+
*/
|
|
1694
|
+
async resolveDraftWriteTarget(id) {
|
|
1695
|
+
const overlay = this.checkedOutDraft;
|
|
1696
|
+
if (!overlay) return id;
|
|
1697
|
+
const existing = overlay.clones[id];
|
|
1698
|
+
if (existing) return existing;
|
|
1699
|
+
if (this.draftDynamicMembers) {
|
|
1700
|
+
if (id === overlay.draftId || this.cloneToOriginal.has(id)) return id;
|
|
1701
|
+
} else if (!this.draftMemberSet.has(id)) {
|
|
1702
|
+
return id;
|
|
1703
|
+
}
|
|
1704
|
+
if (overlay.onMissingMember) {
|
|
1705
|
+
const cloneId = await overlay.onMissingMember(id);
|
|
1706
|
+
if (cloneId && this.checkedOutDraft === overlay) {
|
|
1707
|
+
this.checkedOutDraft = { ...overlay, clones: { ...overlay.clones, [id]: cloneId } };
|
|
1708
|
+
this.cloneToOriginal.set(cloneId, id);
|
|
1709
|
+
this.notifyDraftOverlayListeners();
|
|
1710
|
+
return cloneId;
|
|
1711
|
+
}
|
|
1712
|
+
if (cloneId) return cloneId;
|
|
1713
|
+
}
|
|
1714
|
+
return id;
|
|
1715
|
+
}
|
|
1530
1716
|
/**
|
|
1531
1717
|
* Return the subset of IDs that already exist in materialized storage.
|
|
1532
1718
|
*
|
|
@@ -1606,6 +1792,7 @@ var NodeStore = class {
|
|
|
1606
1792
|
*/
|
|
1607
1793
|
async update(id, options) {
|
|
1608
1794
|
const start = this.telemetry ? Date.now() : 0;
|
|
1795
|
+
id = await this.resolveDraftWriteTarget(id);
|
|
1609
1796
|
try {
|
|
1610
1797
|
if (this.canUseSingleWriteFastPath()) {
|
|
1611
1798
|
const { node: node2, previousNode, change: change2 } = await this.applySingleNodeWrite({
|
|
@@ -1688,6 +1875,7 @@ var NodeStore = class {
|
|
|
1688
1875
|
*/
|
|
1689
1876
|
async delete(id) {
|
|
1690
1877
|
const start = this.telemetry ? Date.now() : 0;
|
|
1878
|
+
id = await this.resolveDraftWriteTarget(id);
|
|
1691
1879
|
try {
|
|
1692
1880
|
if (this.canUseSingleWriteFastPath()) {
|
|
1693
1881
|
const { node, previousNode, change: change2 } = await this.applySingleNodeWrite({
|
|
@@ -1753,6 +1941,7 @@ var NodeStore = class {
|
|
|
1753
1941
|
* Restore a deleted Node.
|
|
1754
1942
|
*/
|
|
1755
1943
|
async restore(id) {
|
|
1944
|
+
id = await this.resolveDraftWriteTarget(id);
|
|
1756
1945
|
if (this.canUseSingleWriteFastPath()) {
|
|
1757
1946
|
const { node: node2, previousNode, change: change2 } = await this.applySingleNodeWrite({
|
|
1758
1947
|
nodeId: id,
|
|
@@ -1821,7 +2010,8 @@ var NodeStore = class {
|
|
|
1821
2010
|
const readable = await this.filterReadableNodes(
|
|
1822
2011
|
decrypted.filter((node) => node !== null)
|
|
1823
2012
|
);
|
|
1824
|
-
const
|
|
2013
|
+
const paged = this.authEvaluator ? this.applyListPagination(readable, options) : readable;
|
|
2014
|
+
const result = await this.overlayStates(paged);
|
|
1825
2015
|
if (this.telemetry) {
|
|
1826
2016
|
this.telemetry.reportPerformance("data.list", Date.now() - start);
|
|
1827
2017
|
this.telemetry.reportUsage("data.list", 1);
|
|
@@ -1840,10 +2030,13 @@ var NodeStore = class {
|
|
|
1840
2030
|
async query(descriptor) {
|
|
1841
2031
|
const start = Date.now();
|
|
1842
2032
|
try {
|
|
2033
|
+
if (this.checkedOutDraft && Object.keys(this.checkedOutDraft.clones).length > 0 && (this.draftMemberSchemaSet === null || this.draftMemberSchemaSet.has(descriptor.schemaId))) {
|
|
2034
|
+
return this.draftAwareQuery(descriptor, start);
|
|
2035
|
+
}
|
|
1843
2036
|
if (this.storage.queryNodes && !this.nodeContentCipher && !this.authEvaluator) {
|
|
1844
2037
|
const result2 = await this.storage.queryNodes(descriptor);
|
|
1845
2038
|
return {
|
|
1846
|
-
nodes: result2.nodes,
|
|
2039
|
+
nodes: await this.overlayStates(result2.nodes),
|
|
1847
2040
|
totalCount: result2.totalCount,
|
|
1848
2041
|
plan: {
|
|
1849
2042
|
...result2.plan,
|
|
@@ -1855,7 +2048,7 @@ var NodeStore = class {
|
|
|
1855
2048
|
const authFingerprint = await this.authFingerprint();
|
|
1856
2049
|
const result2 = await this.storage.queryNodes({ ...descriptor, authFingerprint });
|
|
1857
2050
|
return {
|
|
1858
|
-
nodes: result2.nodes,
|
|
2051
|
+
nodes: await this.overlayStates(result2.nodes),
|
|
1859
2052
|
totalCount: result2.totalCount,
|
|
1860
2053
|
plan: {
|
|
1861
2054
|
...result2.plan,
|
|
@@ -1866,7 +2059,7 @@ var NodeStore = class {
|
|
|
1866
2059
|
if (this.storage.queryNodes && !this.nodeContentCipher && this.authEvaluator && !descriptor.nodeId && descriptor.materializedView === void 0) {
|
|
1867
2060
|
const candidates = await this.storage.queryNodes(withoutNodeQueryPagination(descriptor));
|
|
1868
2061
|
const readable2 = await this.filterReadableNodes(candidates.nodes);
|
|
1869
|
-
const result2 = applyNodeQueryDescriptor(readable2, descriptor);
|
|
2062
|
+
const result2 = await this.overlayStates(applyNodeQueryDescriptor(readable2, descriptor));
|
|
1870
2063
|
return {
|
|
1871
2064
|
nodes: result2,
|
|
1872
2065
|
totalCount: readable2.length,
|
|
@@ -1890,7 +2083,9 @@ var NodeStore = class {
|
|
|
1890
2083
|
const readable = await this.filterReadableNodes(
|
|
1891
2084
|
decrypted.filter((node) => node !== null)
|
|
1892
2085
|
);
|
|
1893
|
-
const result =
|
|
2086
|
+
const result = await this.overlayStates(
|
|
2087
|
+
applyNodeQueryDescriptor(readable, fallback.postFilterDescriptor)
|
|
2088
|
+
);
|
|
1894
2089
|
const totalCount = fallback.totalCount ?? (fallback.paginatedInStorage ? void 0 : applyNodeQueryDescriptor(
|
|
1895
2090
|
readable,
|
|
1896
2091
|
withoutNodeQueryPagination(fallback.postFilterDescriptor)
|
|
@@ -2685,6 +2880,24 @@ var NodeStore = class {
|
|
|
2685
2880
|
}
|
|
2686
2881
|
}
|
|
2687
2882
|
}
|
|
2883
|
+
const originalId = this.cloneToOriginal.get(change.payload.nodeId);
|
|
2884
|
+
if (originalId) {
|
|
2885
|
+
const originalListeners = this.nodeListeners.get(originalId);
|
|
2886
|
+
if (originalListeners) {
|
|
2887
|
+
const mirrored = {
|
|
2888
|
+
...event,
|
|
2889
|
+
node: event.node ? { ...event.node, id: originalId } : event.node,
|
|
2890
|
+
previousNode: event.previousNode ? { ...event.previousNode, id: originalId } : event.previousNode
|
|
2891
|
+
};
|
|
2892
|
+
for (const listener of originalListeners) {
|
|
2893
|
+
try {
|
|
2894
|
+
listener(mirrored);
|
|
2895
|
+
} catch (err) {
|
|
2896
|
+
console.error("Error in NodeStore draft-mirror listener:", err);
|
|
2897
|
+
}
|
|
2898
|
+
}
|
|
2899
|
+
}
|
|
2900
|
+
}
|
|
2688
2901
|
}
|
|
2689
2902
|
// ==========================================================================
|
|
2690
2903
|
// Internal Methods
|
|
@@ -3415,6 +3628,41 @@ var MemoryNodeStorageAdapter = class {
|
|
|
3415
3628
|
this.yjsSnapshotStore = this.yjsSnapshotStore.filter((s) => s.nodeId !== nodeId);
|
|
3416
3629
|
}
|
|
3417
3630
|
// ==========================================================================
|
|
3631
|
+
// Pin Registry (exploration 0329)
|
|
3632
|
+
// ==========================================================================
|
|
3633
|
+
/** key -> ownerId -> entry */
|
|
3634
|
+
pinStore = /* @__PURE__ */ new Map();
|
|
3635
|
+
pins = {
|
|
3636
|
+
addPins: async (pins) => {
|
|
3637
|
+
for (const pin of pins) {
|
|
3638
|
+
let owners = this.pinStore.get(pin.key);
|
|
3639
|
+
if (!owners) {
|
|
3640
|
+
owners = /* @__PURE__ */ new Map();
|
|
3641
|
+
this.pinStore.set(pin.key, owners);
|
|
3642
|
+
}
|
|
3643
|
+
owners.set(pin.ownerId, { ...pin });
|
|
3644
|
+
}
|
|
3645
|
+
},
|
|
3646
|
+
removePinsByOwner: async (ownerId) => {
|
|
3647
|
+
for (const [key, owners] of this.pinStore) {
|
|
3648
|
+
owners.delete(ownerId);
|
|
3649
|
+
if (owners.size === 0) this.pinStore.delete(key);
|
|
3650
|
+
}
|
|
3651
|
+
},
|
|
3652
|
+
getPinnedKeysAmong: async (keys) => {
|
|
3653
|
+
const pinned = /* @__PURE__ */ new Set();
|
|
3654
|
+
for (const key of keys) {
|
|
3655
|
+
if (this.pinStore.has(key)) pinned.add(key);
|
|
3656
|
+
}
|
|
3657
|
+
return pinned;
|
|
3658
|
+
},
|
|
3659
|
+
countPins: async () => {
|
|
3660
|
+
let count2 = 0;
|
|
3661
|
+
for (const owners of this.pinStore.values()) count2 += owners.size;
|
|
3662
|
+
return count2;
|
|
3663
|
+
}
|
|
3664
|
+
};
|
|
3665
|
+
// ==========================================================================
|
|
3418
3666
|
// Utility Methods (for testing)
|
|
3419
3667
|
// ==========================================================================
|
|
3420
3668
|
/**
|
|
@@ -3426,6 +3674,7 @@ var MemoryNodeStorageAdapter = class {
|
|
|
3426
3674
|
this.nodes.clear();
|
|
3427
3675
|
this.documentContentStore.clear();
|
|
3428
3676
|
this.yjsSnapshotStore = [];
|
|
3677
|
+
this.pinStore.clear();
|
|
3429
3678
|
this.lastLamportTime = 0;
|
|
3430
3679
|
this.syncCursors.clear();
|
|
3431
3680
|
}
|
|
@@ -3465,6 +3714,9 @@ var MemoryNodeStorageAdapter = class {
|
|
|
3465
3714
|
])
|
|
3466
3715
|
),
|
|
3467
3716
|
yjsSnapshotStore: structuredClone(this.yjsSnapshotStore),
|
|
3717
|
+
pinStore: new Map(
|
|
3718
|
+
Array.from(this.pinStore.entries(), ([key, owners]) => [key, new Map(owners)])
|
|
3719
|
+
),
|
|
3468
3720
|
lastLamportTime: this.lastLamportTime
|
|
3469
3721
|
};
|
|
3470
3722
|
}
|
|
@@ -3474,6 +3726,7 @@ var MemoryNodeStorageAdapter = class {
|
|
|
3474
3726
|
this.nodes = snapshot.nodes;
|
|
3475
3727
|
this.documentContentStore = snapshot.documentContentStore;
|
|
3476
3728
|
this.yjsSnapshotStore = snapshot.yjsSnapshotStore;
|
|
3729
|
+
this.pinStore = snapshot.pinStore;
|
|
3477
3730
|
this.lastLamportTime = snapshot.lastLamportTime;
|
|
3478
3731
|
}
|
|
3479
3732
|
};
|
|
@@ -4653,7 +4906,7 @@ var SQLiteNodeStorageAdapter = class {
|
|
|
4653
4906
|
nodeReadAuthorizer;
|
|
4654
4907
|
/** One-time guard: ensure the `auth_fingerprint` column exists on upgraded DBs. */
|
|
4655
4908
|
materializationColumnsReady = false;
|
|
4656
|
-
nodePropertyColumnsReady =
|
|
4909
|
+
nodePropertyColumnsReady = null;
|
|
4657
4910
|
/**
|
|
4658
4911
|
* Descriptor→SQL compilation lives in `query-compiler.ts` (exploration
|
|
4659
4912
|
* 0276). Flags are read per-compile so the compiler never captures stale
|
|
@@ -4948,12 +5201,14 @@ var SQLiteNodeStorageAdapter = class {
|
|
|
4948
5201
|
}
|
|
4949
5202
|
// ─── Materialized State Operations ────────────────────────────────────────
|
|
4950
5203
|
async getNode(id) {
|
|
5204
|
+
await this.ensureNodePropertyColumns();
|
|
4951
5205
|
const nodes = await hydrateNodesByIds(this.db, [id], this.aggregatedHydration);
|
|
4952
5206
|
return nodes[0] ?? null;
|
|
4953
5207
|
}
|
|
4954
5208
|
async getNodes(ids) {
|
|
4955
5209
|
const uniqueIds = Array.from(new Set(ids));
|
|
4956
5210
|
if (uniqueIds.length === 0) return [];
|
|
5211
|
+
await this.ensureNodePropertyColumns();
|
|
4957
5212
|
return hydrateNodesByIds(this.db, uniqueIds, this.aggregatedHydration);
|
|
4958
5213
|
}
|
|
4959
5214
|
async getExistingNodeIds(ids) {
|
|
@@ -5075,6 +5330,7 @@ var SQLiteNodeStorageAdapter = class {
|
|
|
5075
5330
|
* Fetches nodes and properties in one query for better performance with large datasets.
|
|
5076
5331
|
*/
|
|
5077
5332
|
async listNodesOptimized(options) {
|
|
5333
|
+
await this.ensureNodePropertyColumns();
|
|
5078
5334
|
let whereClause = "1=1";
|
|
5079
5335
|
const params = [];
|
|
5080
5336
|
if (options?.schemaId) {
|
|
@@ -5136,6 +5392,7 @@ var SQLiteNodeStorageAdapter = class {
|
|
|
5136
5392
|
}
|
|
5137
5393
|
async queryNodes(descriptor) {
|
|
5138
5394
|
const start = Date.now();
|
|
5395
|
+
await this.ensureNodePropertyColumns();
|
|
5139
5396
|
if (descriptor.materializedView) {
|
|
5140
5397
|
return this.queryMaterializedView(descriptor, start);
|
|
5141
5398
|
}
|
|
@@ -5326,6 +5583,47 @@ var SQLiteNodeStorageAdapter = class {
|
|
|
5326
5583
|
[nodeId, content, Date.now()]
|
|
5327
5584
|
);
|
|
5328
5585
|
}
|
|
5586
|
+
// ─── Pin Registry (exploration 0329) ──────────────────────────────────────
|
|
5587
|
+
pins = {
|
|
5588
|
+
addPins: async (pins) => {
|
|
5589
|
+
if (pins.length === 0) return;
|
|
5590
|
+
await this.enqueueWrite(async () => {
|
|
5591
|
+
const now = Date.now();
|
|
5592
|
+
for (const chunk of chunkItems(pins, 200)) {
|
|
5593
|
+
const placeholders = chunk.map(() => "(?, ?, ?, ?)").join(", ");
|
|
5594
|
+
const params = [];
|
|
5595
|
+
for (const pin of chunk) params.push(pin.key, pin.ownerId, pin.reason, now);
|
|
5596
|
+
await this.db.run(
|
|
5597
|
+
`INSERT INTO pinned_changes (pin_key, owner_id, reason, created_at)
|
|
5598
|
+
VALUES ${placeholders}
|
|
5599
|
+
ON CONFLICT(pin_key, owner_id) DO NOTHING`,
|
|
5600
|
+
params
|
|
5601
|
+
);
|
|
5602
|
+
}
|
|
5603
|
+
});
|
|
5604
|
+
},
|
|
5605
|
+
removePinsByOwner: async (ownerId) => {
|
|
5606
|
+
await this.enqueueWrite(async () => {
|
|
5607
|
+
await this.db.run(`DELETE FROM pinned_changes WHERE owner_id = ?`, [ownerId]);
|
|
5608
|
+
});
|
|
5609
|
+
},
|
|
5610
|
+
getPinnedKeysAmong: async (keys) => {
|
|
5611
|
+
const pinned = /* @__PURE__ */ new Set();
|
|
5612
|
+
for (const chunk of chunkItems(keys, 500)) {
|
|
5613
|
+
const placeholders = chunk.map(() => "?").join(", ");
|
|
5614
|
+
const rows = await this.db.query(
|
|
5615
|
+
`SELECT DISTINCT pin_key FROM pinned_changes WHERE pin_key IN (${placeholders})`,
|
|
5616
|
+
[...chunk]
|
|
5617
|
+
);
|
|
5618
|
+
for (const row of rows) pinned.add(row.pin_key);
|
|
5619
|
+
}
|
|
5620
|
+
return pinned;
|
|
5621
|
+
},
|
|
5622
|
+
countPins: async () => {
|
|
5623
|
+
const row = await this.db.queryOne(`SELECT COUNT(*) AS n FROM pinned_changes`);
|
|
5624
|
+
return row?.n ?? 0;
|
|
5625
|
+
}
|
|
5626
|
+
};
|
|
5329
5627
|
// ─── Yjs Snapshots (Extended) ─────────────────────────────────────────────
|
|
5330
5628
|
/**
|
|
5331
5629
|
* Save a Yjs snapshot for time travel.
|
|
@@ -5953,15 +6251,16 @@ var SQLiteNodeStorageAdapter = class {
|
|
|
5953
6251
|
* just falls back to the author-DID tiebreak.
|
|
5954
6252
|
*/
|
|
5955
6253
|
async ensureNodePropertyColumns() {
|
|
5956
|
-
|
|
5957
|
-
|
|
5958
|
-
|
|
5959
|
-
|
|
5960
|
-
|
|
6254
|
+
this.nodePropertyColumnsReady ??= (async () => {
|
|
6255
|
+
try {
|
|
6256
|
+
const columns = await this.db.query(`PRAGMA table_info(node_properties)`);
|
|
6257
|
+
if (!columns.some((column) => column.name === "tiebreak_key")) {
|
|
6258
|
+
await this.db.run("ALTER TABLE node_properties ADD COLUMN tiebreak_key TEXT");
|
|
6259
|
+
}
|
|
6260
|
+
} catch {
|
|
5961
6261
|
}
|
|
5962
|
-
}
|
|
5963
|
-
|
|
5964
|
-
this.nodePropertyColumnsReady = true;
|
|
6262
|
+
})();
|
|
6263
|
+
return this.nodePropertyColumnsReady;
|
|
5965
6264
|
}
|
|
5966
6265
|
setNodeReadAuthorizer(authorizer) {
|
|
5967
6266
|
this.nodeReadAuthorizer = authorizer;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import {
|
|
2
|
+
spaceContributorAuthorization
|
|
3
|
+
} from "./chunk-JA56EQJO.js";
|
|
4
|
+
import {
|
|
5
|
+
created,
|
|
6
|
+
createdBy,
|
|
7
|
+
defineSchema,
|
|
8
|
+
json,
|
|
9
|
+
relation,
|
|
10
|
+
text
|
|
11
|
+
} from "./chunk-RRXJNZX5.js";
|
|
12
|
+
|
|
13
|
+
// src/schema/schemas/checkpoint.ts
|
|
14
|
+
var CHECKPOINT_SCHEMA_IRI = "xnet://xnet.fyi/Checkpoint@1.0.0";
|
|
15
|
+
var CheckpointSchema = defineSchema({
|
|
16
|
+
name: "Checkpoint",
|
|
17
|
+
namespace: "xnet://xnet.fyi/",
|
|
18
|
+
properties: {
|
|
19
|
+
/** User-given version name ("Draft sent to review", "Before the rewrite") */
|
|
20
|
+
name: text({ required: true, maxLength: 200 }),
|
|
21
|
+
/** Optional longer note shown in the Time Machine */
|
|
22
|
+
note: text({ maxLength: 2e3 }),
|
|
23
|
+
/** Per-node frontier: nodeId -> { hash, yjsSnapshotRef? } */
|
|
24
|
+
frontier: json({}),
|
|
25
|
+
/** The scope this checkpoint was taken over (page, database, space, …) */
|
|
26
|
+
scope: relation({}),
|
|
27
|
+
/** Space containment (single-valued) — drives the authorization roles */
|
|
28
|
+
space: relation({ target: "xnet://xnet.fyi/Space@1.0.0" }),
|
|
29
|
+
createdAt: created(),
|
|
30
|
+
createdBy: createdBy()
|
|
31
|
+
},
|
|
32
|
+
document: void 0,
|
|
33
|
+
// Checkpoints of shared content are visible to the space that content
|
|
34
|
+
// lives in; contributors can create their own.
|
|
35
|
+
authorization: spaceContributorAuthorization()
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
export {
|
|
39
|
+
CHECKPOINT_SCHEMA_IRI,
|
|
40
|
+
CheckpointSchema
|
|
41
|
+
};
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createNodeQueryDescriptor
|
|
3
|
+
} from "./chunk-S5RP5RKY.js";
|
|
1
4
|
import {
|
|
2
5
|
extKey
|
|
3
6
|
} from "./chunk-PMUQACPY.js";
|
|
@@ -26,9 +29,6 @@ import {
|
|
|
26
29
|
import {
|
|
27
30
|
DatabaseViewSchema
|
|
28
31
|
} from "./chunk-TRPXLCND.js";
|
|
29
|
-
import {
|
|
30
|
-
createNodeQueryDescriptor
|
|
31
|
-
} from "./chunk-S5RP5RKY.js";
|
|
32
32
|
|
|
33
33
|
// src/database/cell-types.ts
|
|
34
34
|
var CELL_PREFIX = "cell_";
|
|
@@ -264,6 +264,30 @@ function getRichTextPlainText(doc, columnId) {
|
|
|
264
264
|
const fragment = doc.getXmlFragment(`${RICHTEXT_PREFIX}${columnId}`);
|
|
265
265
|
return extractPlainText(fragment);
|
|
266
266
|
}
|
|
267
|
+
var BLOCK_ELEMENT_NAMES = /* @__PURE__ */ new Set([
|
|
268
|
+
// HTML-ish / legacy names
|
|
269
|
+
"p",
|
|
270
|
+
"h1",
|
|
271
|
+
"h2",
|
|
272
|
+
"h3",
|
|
273
|
+
"h4",
|
|
274
|
+
"h5",
|
|
275
|
+
"h6",
|
|
276
|
+
"li",
|
|
277
|
+
"blockquote",
|
|
278
|
+
// TipTap / BlockNote shared names
|
|
279
|
+
"paragraph",
|
|
280
|
+
"heading",
|
|
281
|
+
"codeBlock",
|
|
282
|
+
"listItem",
|
|
283
|
+
"taskItem",
|
|
284
|
+
// BlockNote (v4) block-content names
|
|
285
|
+
"bulletListItem",
|
|
286
|
+
"numberedListItem",
|
|
287
|
+
"checkListItem",
|
|
288
|
+
"toggleListItem",
|
|
289
|
+
"quote"
|
|
290
|
+
]);
|
|
267
291
|
function extractPlainText(fragment) {
|
|
268
292
|
const parts = [];
|
|
269
293
|
for (let i = 0; i < fragment.length; i++) {
|
|
@@ -276,7 +300,33 @@ function extractPlainText(fragment) {
|
|
|
276
300
|
}
|
|
277
301
|
return parts.join("");
|
|
278
302
|
}
|
|
303
|
+
function inlineAtomText(element) {
|
|
304
|
+
const attr = (key) => {
|
|
305
|
+
const value = element.getAttribute(key);
|
|
306
|
+
return typeof value === "string" ? value : "";
|
|
307
|
+
};
|
|
308
|
+
switch (element.nodeName) {
|
|
309
|
+
case "mention": {
|
|
310
|
+
const label = attr("label") || attr("id");
|
|
311
|
+
return label ? `@${label}` : "";
|
|
312
|
+
}
|
|
313
|
+
case "hashtag": {
|
|
314
|
+
const name = attr("name");
|
|
315
|
+
return name ? `#${name}` : "";
|
|
316
|
+
}
|
|
317
|
+
case "wikilink":
|
|
318
|
+
return element.length === 0 ? attr("title") : null;
|
|
319
|
+
case "inlineMath":
|
|
320
|
+
return attr("latex");
|
|
321
|
+
default:
|
|
322
|
+
return null;
|
|
323
|
+
}
|
|
324
|
+
}
|
|
279
325
|
function extractPlainTextFromElement(element) {
|
|
326
|
+
const atomText = inlineAtomText(element);
|
|
327
|
+
if (atomText !== null) {
|
|
328
|
+
return atomText;
|
|
329
|
+
}
|
|
280
330
|
const parts = [];
|
|
281
331
|
for (let i = 0; i < element.length; i++) {
|
|
282
332
|
const item = element.get(i);
|
|
@@ -286,8 +336,7 @@ function extractPlainTextFromElement(element) {
|
|
|
286
336
|
parts.push(extractPlainTextFromElement(item));
|
|
287
337
|
}
|
|
288
338
|
}
|
|
289
|
-
|
|
290
|
-
if (blockElements.includes(element.nodeName)) {
|
|
339
|
+
if (BLOCK_ELEMENT_NAMES.has(element.nodeName)) {
|
|
291
340
|
parts.push("\n");
|
|
292
341
|
}
|
|
293
342
|
return parts.join("");
|