@nkzw/fate 1.3.3 → 1.5.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/README.md +326 -0
- package/docs/api/classes/FateClient.md +51 -199
- package/docs/api/functions/clientRoot.md +1 -1
- package/docs/api/functions/createClient.md +1 -1
- package/docs/api/functions/createGraphQLTransport.md +1 -1
- package/docs/api/functions/createHTTPTransport.md +1 -1
- package/docs/api/functions/createTRPCTransport.md +5 -1
- package/docs/api/functions/defer.md +1 -1
- package/docs/api/functions/getDeferredMetadata.md +1 -1
- package/docs/api/functions/getListEntries.md +1 -1
- package/docs/api/functions/getSelectionPlan.md +1 -1
- package/docs/api/functions/graphqlMutation.md +1 -1
- package/docs/api/functions/isDeferred.md +1 -1
- package/docs/api/functions/isRecord.md +1 -1
- package/docs/api/functions/isViewTag.md +1 -1
- package/docs/api/functions/liveConnectionTopic.md +1 -1
- package/docs/api/functions/liveEntityTopic.md +1 -1
- package/docs/api/functions/liveGlobalConnectionTopic.md +1 -1
- package/docs/api/functions/mutation.md +1 -1
- package/docs/api/functions/toEntityId.md +1 -1
- package/docs/api/functions/view.md +1 -1
- package/docs/api/index.md +4 -1
- package/docs/api/interfaces/FateThenable.md +3 -3
- package/docs/api/interfaces/Persistence.md +23 -0
- package/docs/api/interfaces/PersistenceSession.md +109 -0
- package/docs/api/interfaces/Transport.md +46 -7
- package/docs/api/server/classes/FateRequestError.md +5 -5
- package/docs/api/server/drizzle/functions/createDrizzleFate.md +1 -1
- package/docs/api/server/drizzle/functions/createDrizzleSourceAdapter.md +1 -1
- package/docs/api/server/drizzle/functions/createDrizzleSourceRegistry.md +1 -1
- package/docs/api/server/drizzle/type-aliases/DrizzleManyToManyConfig.md +1 -1
- package/docs/api/server/drizzle/type-aliases/DrizzleManyToManyInput.md +1 -1
- package/docs/api/server/drizzle/type-aliases/DrizzleQueryExtra.md +3 -3
- package/docs/api/server/drizzle/type-aliases/DrizzleSourceAdapter.md +9 -9
- package/docs/api/server/drizzle/type-aliases/DrizzleViewConfig.md +1 -1
- package/docs/api/server/functions/bindSourceProcedures.md +1 -1
- package/docs/api/server/functions/computed.md +1 -1
- package/docs/api/server/functions/count.md +1 -1
- package/docs/api/server/functions/createFateFetchHandler.md +1 -1
- package/docs/api/server/functions/createFateServer.md +1 -1
- package/docs/api/server/functions/createHonoFateHandler.md +1 -1
- package/docs/api/server/functions/createLiveEventBus.md +1 -1
- package/docs/api/server/functions/createNestedSourcePlan.md +1 -1
- package/docs/api/server/functions/createResolver.md +1 -1
- package/docs/api/server/functions/createSourcePlan.md +1 -1
- package/docs/api/server/functions/createSourceProcedures.md +1 -1
- package/docs/api/server/functions/dataView.md +1 -1
- package/docs/api/server/functions/field.md +1 -1
- package/docs/api/server/functions/getNestedSelection.md +1 -1
- package/docs/api/server/functions/getScopedArgs.md +1 -1
- package/docs/api/server/functions/hasNestedSelection.md +1 -1
- package/docs/api/server/functions/list.md +1 -1
- package/docs/api/server/functions/refetchSourceById.md +1 -1
- package/docs/api/server/functions/resolveSourceById.md +1 -1
- package/docs/api/server/functions/resolveSourceByIds.md +1 -1
- package/docs/api/server/functions/resolveSourceConnection.md +1 -1
- package/docs/api/server/functions/resolver.md +1 -1
- package/docs/api/server/functions/toPrismaSelect.md +1 -1
- package/docs/api/server/functions/withConnection.md +1 -1
- package/docs/api/server/prisma/functions/createPrismaFate.md +1 -1
- package/docs/api/server/prisma/functions/createPrismaSourceAdapter.md +1 -1
- package/docs/api/server/prisma/functions/createPrismaSourceRegistry.md +1 -1
- package/docs/api/server/prisma/functions/prismaConnectionArgs.md +1 -1
- package/docs/api/server/prisma/type-aliases/PrismaDelegate.md +4 -4
- package/docs/api/server/prisma/type-aliases/PrismaQueryExtra.md +1 -1
- package/docs/api/server/prisma/type-aliases/PrismaSourceAdapter.md +9 -9
- package/docs/api/server/prisma/type-aliases/PrismaViewConfig.md +1 -1
- package/docs/api/server/type-aliases/ComputedField.md +5 -5
- package/docs/api/server/type-aliases/ComputedSelection.md +1 -1
- package/docs/api/server/type-aliases/ConnectionItem.md +3 -3
- package/docs/api/server/type-aliases/ConnectionPagination.md +5 -5
- package/docs/api/server/type-aliases/ConnectionResult.md +3 -3
- package/docs/api/server/type-aliases/CountSelection.md +4 -4
- package/docs/api/server/type-aliases/CountWhere.md +1 -1
- package/docs/api/server/type-aliases/DataViewListOptions.md +2 -2
- package/docs/api/server/type-aliases/DataViewOrderBy.md +1 -1
- package/docs/api/server/type-aliases/DataViewOrderDirection.md +1 -1
- package/docs/api/server/type-aliases/DataViewResult.md +1 -1
- package/docs/api/server/type-aliases/Entity.md +1 -1
- package/docs/api/server/type-aliases/FateServer.md +5 -5
- package/docs/api/server/type-aliases/FateServerManifest.md +1 -1
- package/docs/api/server/type-aliases/FieldSelection.md +3 -3
- package/docs/api/server/type-aliases/LiveConnectionEventType.md +1 -1
- package/docs/api/server/type-aliases/LiveConnectionSourceEvent.md +1 -1
- package/docs/api/server/type-aliases/LiveEventBus.md +1 -1
- package/docs/api/server/type-aliases/LiveEventType.md +1 -1
- package/docs/api/server/type-aliases/LiveSourceEvent.md +1 -1
- package/docs/api/server/type-aliases/NativeFateAPI.md +4 -4
- package/docs/api/server/type-aliases/OrderDirection.md +1 -1
- package/docs/api/server/type-aliases/SourceConfig.md +6 -6
- package/docs/api/server/type-aliases/SourceDefinition.md +6 -6
- package/docs/api/server/type-aliases/SourceOrder.md +1 -1
- package/docs/api/server/type-aliases/SourceOrderField.md +3 -3
- package/docs/api/server/type-aliases/SourcePlan.md +1 -1
- package/docs/api/server/type-aliases/SourcePlanNode.md +1 -1
- package/docs/api/server/type-aliases/SourceRegistry.md +1 -1
- package/docs/api/server/type-aliases/SourceRelation.md +1 -1
- package/docs/api/server/type-aliases/SourceRelationConfig.md +1 -1
- package/docs/api/server/variables/byIdInput.md +1 -1
- package/docs/api/server/variables/connectionArgs.md +1 -1
- package/docs/api/type-aliases/ConnectionMetadata.md +1 -1
- package/docs/api/type-aliases/ConnectionRef.md +1 -1
- package/docs/api/type-aliases/Deferred.md +1 -1
- package/docs/api/type-aliases/DeferredMetadata.md +1 -1
- package/docs/api/type-aliases/DeferredSelection.md +1 -1
- package/docs/api/type-aliases/DeferredSnapshot.md +1 -1
- package/docs/api/type-aliases/Entity.md +2 -2
- package/docs/api/type-aliases/EntityId.md +1 -1
- package/docs/api/type-aliases/FateDehydratedState.md +1 -1
- package/docs/api/type-aliases/FateLiveConnectionEvent.md +1 -1
- package/docs/api/type-aliases/FateLiveEvent.md +1 -1
- package/docs/api/type-aliases/FateMutations.md +1 -1
- package/docs/api/type-aliases/FateOperation.md +2 -2
- package/docs/api/type-aliases/FateProtocolRequest.md +1 -1
- package/docs/api/type-aliases/FateProtocolResponse.md +1 -1
- package/docs/api/type-aliases/FateRecord.md +1 -1
- package/docs/api/type-aliases/FateRoots.md +1 -1
- package/docs/api/type-aliases/GraphQLMutationDefinition.md +1 -1
- package/docs/api/type-aliases/GraphQLMutationInput.md +1 -1
- package/docs/api/type-aliases/GraphQLMutationMap.md +1 -1
- package/docs/api/type-aliases/GraphQLMutationOutput.md +1 -1
- package/docs/api/type-aliases/GraphQLTransportOptions.md +19 -11
- package/docs/api/type-aliases/HydrateOptions.md +1 -1
- package/docs/api/type-aliases/HydrationLimits.md +1 -1
- package/docs/api/type-aliases/InferFateAPI.md +1 -1
- package/docs/api/type-aliases/List.md +1 -1
- package/docs/api/type-aliases/ListEntry.md +1 -1
- package/docs/api/type-aliases/ListItem.md +1 -1
- package/docs/api/type-aliases/Mask.md +1 -1
- package/docs/api/type-aliases/MutationDefinition.md +1 -1
- package/docs/api/type-aliases/MutationEntity.md +1 -1
- package/docs/api/type-aliases/MutationIdentifier.md +1 -1
- package/docs/api/type-aliases/MutationIdentity.md +7 -0
- package/docs/api/type-aliases/MutationInput.md +1 -1
- package/docs/api/type-aliases/MutationResult.md +1 -1
- package/docs/api/type-aliases/NodesItem.md +1 -1
- package/docs/api/type-aliases/Pagination.md +5 -5
- package/docs/api/type-aliases/PersistenceSnapshot.md +5 -0
- package/docs/api/type-aliases/Request.md +1 -1
- package/docs/api/type-aliases/RequestMode.md +1 -1
- package/docs/api/type-aliases/RequestOptions.md +2 -2
- package/docs/api/type-aliases/RequestResult.md +1 -1
- package/docs/api/type-aliases/Selection.md +1 -1
- package/docs/api/type-aliases/TypeConfig.md +4 -4
- package/docs/api/type-aliases/View.md +1 -1
- package/docs/api/type-aliases/ViewData.md +1 -1
- package/docs/api/type-aliases/ViewEntity.md +1 -1
- package/docs/api/type-aliases/ViewEntityName.md +1 -1
- package/docs/api/type-aliases/ViewRef.md +1 -1
- package/docs/api/type-aliases/ViewSelection.md +1 -1
- package/docs/api/type-aliases/ViewSnapshot.md +1 -1
- package/docs/api/type-aliases/ViewTag.md +1 -1
- package/docs/api/variables/ConnectionTag.md +1 -1
- package/docs/api/variables/DeferTag.md +1 -1
- package/docs/api/variables/DeferredTag.md +1 -1
- package/docs/guide/getting-started.md +2 -0
- package/docs/guide/persistence.md +323 -0
- package/lib/{liveTopics-uVP0LG-Q.mjs → args-CRLIRl_v.mjs} +1 -59
- package/lib/cli.d.mts +1 -1
- package/lib/{executor-C8HNogYG.d.mts → executor-DoiTBiVv.d.mts} +18 -65
- package/lib/{graphqlTransport-DskuXeUP.d.mts → graphqlTransport-DGLYyRxL.d.mts} +4 -14
- package/lib/{graphqlTransport-DSBVS2Fq.mjs → graphqlTransport-DmheG3AO.mjs} +3 -2
- package/lib/graphqlTransport.d.mts +1 -1
- package/lib/graphqlTransport.mjs +1 -1
- package/lib/hydration-DPZRJgnu.mjs +273 -0
- package/lib/idempotency-types-CqA-BFpn.d.mts +15 -0
- package/lib/idempotency.d.mts +26 -0
- package/lib/idempotency.mjs +34 -0
- package/lib/index.d.mts +6 -15
- package/lib/index.mjs +560 -1774
- package/lib/{list-sI2UPhHc.d.mts → list-BRT1VtiT.d.mts} +1 -2
- package/lib/list.d.mts +1 -1
- package/lib/liveTopics-DuFkDZQm.mjs +17 -0
- package/lib/persistence.d.mts +38 -0
- package/lib/persistence.mjs +1529 -0
- package/lib/protocol-DS5sf6sd.mjs +44 -0
- package/lib/{record-CZyyeLa3.d.mts → record-DITM4zb_.d.mts} +14 -19
- package/lib/server/drizzle.d.mts +45 -114
- package/lib/server/drizzle.mjs +2 -2
- package/lib/server/prisma.d.mts +52 -127
- package/lib/server/prisma.mjs +9 -8
- package/lib/server.d.mts +4 -16
- package/lib/server.mjs +21 -35
- package/lib/sortObjectKeys-yhuitEQZ.mjs +9 -0
- package/lib/{sourceRouter-DSaADXSf.mjs → sourceRouter-51POBlEE.mjs} +9 -6
- package/lib/store-CeatPJeK.mjs +1350 -0
- package/lib/{transport-DhaVVq4P.d.mts → transport-DqjhjLfC.d.mts} +672 -517
- package/lib/vite.d.mts +0 -1
- package/lib/vite.mjs +19 -11
- package/package.json +15 -5
- package/docs/api/type-aliases/Snapshot.md +0 -7
|
@@ -0,0 +1,1350 @@
|
|
|
1
|
+
import { t as isRecord } from "./record-B07VwXd-.mjs";
|
|
2
|
+
import { a as hashArgs, n as cloneArgs, o as paginationArgKeys } from "./args-CRLIRl_v.mjs";
|
|
3
|
+
import { d as MutationKind, h as getViewTag, l as DeferTag, m as ViewsTag, o as getNodeRefId, p as ViewKind, s as isNodeRef, u as DeferredTag, y as isViewTag } from "./hydration-DPZRJgnu.mjs";
|
|
4
|
+
import { t as FateRequestError } from "./protocol-DS5sf6sd.mjs";
|
|
5
|
+
//#region src/defer.ts
|
|
6
|
+
function defer(selection) {
|
|
7
|
+
return Object.freeze({ [DeferTag]: selection });
|
|
8
|
+
}
|
|
9
|
+
const isDeferredSelection = (value) => typeof value === "object" && value !== null && DeferTag in value;
|
|
10
|
+
const getDeferredSelection = (selection) => selection[DeferTag];
|
|
11
|
+
const createDeferred = (metadata) => Object.freeze({ [DeferredTag]: metadata });
|
|
12
|
+
const isDeferred = (value) => typeof value === "object" && value !== null && DeferredTag in value;
|
|
13
|
+
const getDeferredMetadata = (deferred) => deferred[DeferredTag];
|
|
14
|
+
//#endregion
|
|
15
|
+
//#region ../../node_modules/.pnpm/@trpc+server@11.18.0_typescript@7.0.2/node_modules/@trpc/server/dist/getErrorShape-BPSzUA7W.mjs
|
|
16
|
+
var __create = Object.create;
|
|
17
|
+
var __defProp = Object.defineProperty;
|
|
18
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
19
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
20
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
21
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
22
|
+
var __commonJS = (cb, mod) => function() {
|
|
23
|
+
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
|
|
24
|
+
};
|
|
25
|
+
var __copyProps = (to, from, except, desc) => {
|
|
26
|
+
if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
|
|
27
|
+
key = keys[i];
|
|
28
|
+
if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
|
|
29
|
+
get: ((k) => from[k]).bind(null, key),
|
|
30
|
+
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
return to;
|
|
34
|
+
};
|
|
35
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
|
|
36
|
+
value: mod,
|
|
37
|
+
enumerable: true
|
|
38
|
+
}) : target, mod));
|
|
39
|
+
const JSONRPC2_TO_HTTP_CODE = {
|
|
40
|
+
PARSE_ERROR: 400,
|
|
41
|
+
BAD_REQUEST: 400,
|
|
42
|
+
UNAUTHORIZED: 401,
|
|
43
|
+
PAYMENT_REQUIRED: 402,
|
|
44
|
+
FORBIDDEN: 403,
|
|
45
|
+
NOT_FOUND: 404,
|
|
46
|
+
METHOD_NOT_SUPPORTED: 405,
|
|
47
|
+
TIMEOUT: 408,
|
|
48
|
+
CONFLICT: 409,
|
|
49
|
+
PRECONDITION_FAILED: 412,
|
|
50
|
+
PAYLOAD_TOO_LARGE: 413,
|
|
51
|
+
UNSUPPORTED_MEDIA_TYPE: 415,
|
|
52
|
+
UNPROCESSABLE_CONTENT: 422,
|
|
53
|
+
PRECONDITION_REQUIRED: 428,
|
|
54
|
+
TOO_MANY_REQUESTS: 429,
|
|
55
|
+
CLIENT_CLOSED_REQUEST: 499,
|
|
56
|
+
INTERNAL_SERVER_ERROR: 500,
|
|
57
|
+
NOT_IMPLEMENTED: 501,
|
|
58
|
+
BAD_GATEWAY: 502,
|
|
59
|
+
SERVICE_UNAVAILABLE: 503,
|
|
60
|
+
GATEWAY_TIMEOUT: 504
|
|
61
|
+
};
|
|
62
|
+
function getStatusCodeFromKey(code) {
|
|
63
|
+
var _JSONRPC2_TO_HTTP_COD;
|
|
64
|
+
return (_JSONRPC2_TO_HTTP_COD = JSONRPC2_TO_HTTP_CODE[code]) !== null && _JSONRPC2_TO_HTTP_COD !== void 0 ? _JSONRPC2_TO_HTTP_COD : 500;
|
|
65
|
+
}
|
|
66
|
+
function getHTTPStatusCodeFromError(error) {
|
|
67
|
+
return getStatusCodeFromKey(error.code);
|
|
68
|
+
}
|
|
69
|
+
var require_typeof = __commonJS({ "../../node_modules/.pnpm/@oxc-project+runtime@0.72.2/node_modules/@oxc-project/runtime/src/helpers/typeof.js"(exports, module) {
|
|
70
|
+
function _typeof$2(o) {
|
|
71
|
+
"@babel/helpers - typeof";
|
|
72
|
+
return module.exports = _typeof$2 = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function(o$1) {
|
|
73
|
+
return typeof o$1;
|
|
74
|
+
} : function(o$1) {
|
|
75
|
+
return o$1 && "function" == typeof Symbol && o$1.constructor === Symbol && o$1 !== Symbol.prototype ? "symbol" : typeof o$1;
|
|
76
|
+
}, module.exports.__esModule = true, module.exports["default"] = module.exports, _typeof$2(o);
|
|
77
|
+
}
|
|
78
|
+
module.exports = _typeof$2, module.exports.__esModule = true, module.exports["default"] = module.exports;
|
|
79
|
+
} });
|
|
80
|
+
var require_toPrimitive = __commonJS({ "../../node_modules/.pnpm/@oxc-project+runtime@0.72.2/node_modules/@oxc-project/runtime/src/helpers/toPrimitive.js"(exports, module) {
|
|
81
|
+
var _typeof$1 = require_typeof()["default"];
|
|
82
|
+
function toPrimitive$1(t, r) {
|
|
83
|
+
if ("object" != _typeof$1(t) || !t) return t;
|
|
84
|
+
var e = t[Symbol.toPrimitive];
|
|
85
|
+
if (void 0 !== e) {
|
|
86
|
+
var i = e.call(t, r || "default");
|
|
87
|
+
if ("object" != _typeof$1(i)) return i;
|
|
88
|
+
throw new TypeError("@@toPrimitive must return a primitive value.");
|
|
89
|
+
}
|
|
90
|
+
return ("string" === r ? String : Number)(t);
|
|
91
|
+
}
|
|
92
|
+
module.exports = toPrimitive$1, module.exports.__esModule = true, module.exports["default"] = module.exports;
|
|
93
|
+
} });
|
|
94
|
+
var require_toPropertyKey = __commonJS({ "../../node_modules/.pnpm/@oxc-project+runtime@0.72.2/node_modules/@oxc-project/runtime/src/helpers/toPropertyKey.js"(exports, module) {
|
|
95
|
+
var _typeof = require_typeof()["default"];
|
|
96
|
+
var toPrimitive = require_toPrimitive();
|
|
97
|
+
function toPropertyKey$1(t) {
|
|
98
|
+
var i = toPrimitive(t, "string");
|
|
99
|
+
return "symbol" == _typeof(i) ? i : i + "";
|
|
100
|
+
}
|
|
101
|
+
module.exports = toPropertyKey$1, module.exports.__esModule = true, module.exports["default"] = module.exports;
|
|
102
|
+
} });
|
|
103
|
+
var require_defineProperty = __commonJS({ "../../node_modules/.pnpm/@oxc-project+runtime@0.72.2/node_modules/@oxc-project/runtime/src/helpers/defineProperty.js"(exports, module) {
|
|
104
|
+
var toPropertyKey = require_toPropertyKey();
|
|
105
|
+
function _defineProperty(e, r, t) {
|
|
106
|
+
return (r = toPropertyKey(r)) in e ? Object.defineProperty(e, r, {
|
|
107
|
+
value: t,
|
|
108
|
+
enumerable: !0,
|
|
109
|
+
configurable: !0,
|
|
110
|
+
writable: !0
|
|
111
|
+
}) : e[r] = t, e;
|
|
112
|
+
}
|
|
113
|
+
module.exports = _defineProperty, module.exports.__esModule = true, module.exports["default"] = module.exports;
|
|
114
|
+
} });
|
|
115
|
+
__toESM(__commonJS({ "../../node_modules/.pnpm/@oxc-project+runtime@0.72.2/node_modules/@oxc-project/runtime/src/helpers/objectSpread2.js"(exports, module) {
|
|
116
|
+
var defineProperty = require_defineProperty();
|
|
117
|
+
function ownKeys(e, r) {
|
|
118
|
+
var t = Object.keys(e);
|
|
119
|
+
if (Object.getOwnPropertySymbols) {
|
|
120
|
+
var o = Object.getOwnPropertySymbols(e);
|
|
121
|
+
r && (o = o.filter(function(r$1) {
|
|
122
|
+
return Object.getOwnPropertyDescriptor(e, r$1).enumerable;
|
|
123
|
+
})), t.push.apply(t, o);
|
|
124
|
+
}
|
|
125
|
+
return t;
|
|
126
|
+
}
|
|
127
|
+
function _objectSpread2(e) {
|
|
128
|
+
for (var r = 1; r < arguments.length; r++) {
|
|
129
|
+
var t = null != arguments[r] ? arguments[r] : {};
|
|
130
|
+
r % 2 ? ownKeys(Object(t), !0).forEach(function(r$1) {
|
|
131
|
+
defineProperty(e, r$1, t[r$1]);
|
|
132
|
+
}) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function(r$1) {
|
|
133
|
+
Object.defineProperty(e, r$1, Object.getOwnPropertyDescriptor(t, r$1));
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
return e;
|
|
137
|
+
}
|
|
138
|
+
module.exports = _objectSpread2, module.exports.__esModule = true, module.exports["default"] = module.exports;
|
|
139
|
+
} })(), 1);
|
|
140
|
+
//#endregion
|
|
141
|
+
//#region src/view.ts
|
|
142
|
+
/**
|
|
143
|
+
* Collects all view payloads that apply to the given ref.
|
|
144
|
+
*/
|
|
145
|
+
const getViewPayloads = (view, ref) => {
|
|
146
|
+
const result = [];
|
|
147
|
+
for (const [key, value] of Object.entries(view)) if (isViewTag(key) && (!ref || ref[ViewsTag]?.has(key))) result.push(value);
|
|
148
|
+
return result;
|
|
149
|
+
};
|
|
150
|
+
/**
|
|
151
|
+
* Returns the set of view tags defined on a view composition.
|
|
152
|
+
*/
|
|
153
|
+
const getViewNames = (view) => {
|
|
154
|
+
const result = /* @__PURE__ */ new Set();
|
|
155
|
+
for (const key of Object.keys(view)) if (isViewTag(key)) result.add(key);
|
|
156
|
+
return result;
|
|
157
|
+
};
|
|
158
|
+
/**
|
|
159
|
+
* Extracts view tags from a nested selection object.
|
|
160
|
+
*/
|
|
161
|
+
const getSelectionViewNames = (selection) => {
|
|
162
|
+
return getViewNames(selection);
|
|
163
|
+
};
|
|
164
|
+
let id = 0;
|
|
165
|
+
const importMetaEnvironment = import.meta.env;
|
|
166
|
+
const isDevelopment = typeof importMetaEnvironment?.DEV === "boolean" ? importMetaEnvironment.DEV : typeof process !== "undefined" ? process.env.NODE_ENV !== "production" : false;
|
|
167
|
+
let viewModulePath = null;
|
|
168
|
+
const getStableId = () => {
|
|
169
|
+
if (isDevelopment) try {
|
|
170
|
+
if (viewModulePath == null) viewModulePath = new URL(import.meta.url).pathname;
|
|
171
|
+
const stack = (/* @__PURE__ */ new Error()).stack?.split("\n");
|
|
172
|
+
if (stack) for (let i = 1; i < stack.length; i++) {
|
|
173
|
+
const match = stack[i].trim().match(/\(?([^()]+):(\d+):(\d+)\)?$/);
|
|
174
|
+
if (!match) continue;
|
|
175
|
+
const [, source, line, column] = match;
|
|
176
|
+
if (!source.includes(viewModulePath)) {
|
|
177
|
+
const file = source.startsWith("at ") ? source.slice(3) : source;
|
|
178
|
+
return `${/^[A-Za-z][\d+.A-Za-z-]*:/.test(file) ? new URL(file).pathname : file}:${line}:${column}`;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
} catch {}
|
|
182
|
+
return String(id++);
|
|
183
|
+
};
|
|
184
|
+
/**
|
|
185
|
+
* Creates a reusable view for an object using the declared selection.
|
|
186
|
+
*
|
|
187
|
+
* @example
|
|
188
|
+
* const PostView = view<Post>()({
|
|
189
|
+
* id: true,
|
|
190
|
+
* title: true,
|
|
191
|
+
* });
|
|
192
|
+
*/
|
|
193
|
+
function view() {
|
|
194
|
+
const viewId = getStableId();
|
|
195
|
+
return (select) => {
|
|
196
|
+
const payload = Object.freeze({
|
|
197
|
+
select,
|
|
198
|
+
[ViewKind]: true
|
|
199
|
+
});
|
|
200
|
+
const viewComposition = Object.defineProperty({}, getViewTag(viewId), {
|
|
201
|
+
configurable: false,
|
|
202
|
+
enumerable: true,
|
|
203
|
+
value: payload,
|
|
204
|
+
writable: false
|
|
205
|
+
});
|
|
206
|
+
return Object.freeze(viewComposition);
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
//#endregion
|
|
210
|
+
//#region src/ref.ts
|
|
211
|
+
/**
|
|
212
|
+
* Builds the canonical cache ID for an entity.
|
|
213
|
+
*/
|
|
214
|
+
const toEntityId = (type, rawId) => `${type}:${String(rawId)}`;
|
|
215
|
+
/**
|
|
216
|
+
* Splits a cache entity ID back into its type and raw identifier.
|
|
217
|
+
*/
|
|
218
|
+
function parseEntityId(id) {
|
|
219
|
+
const idx = id.indexOf(":");
|
|
220
|
+
return idx < 0 ? {
|
|
221
|
+
id,
|
|
222
|
+
type: ""
|
|
223
|
+
} : {
|
|
224
|
+
id: id.slice(idx + 1),
|
|
225
|
+
type: id.slice(0, idx)
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* Attaches view tags to a ref without leaking the symbol.
|
|
230
|
+
*/
|
|
231
|
+
function assignViewTag(target, value) {
|
|
232
|
+
Object.defineProperty(target, ViewsTag, {
|
|
233
|
+
configurable: false,
|
|
234
|
+
enumerable: false,
|
|
235
|
+
value,
|
|
236
|
+
writable: false
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
const getRootViewNames = (view) => {
|
|
240
|
+
const names = new Set(getViewNames(view));
|
|
241
|
+
const payloads = getViewPayloads(view, null);
|
|
242
|
+
for (const payload of payloads) for (const name of getSelectionViewNames(payload.select)) names.add(name);
|
|
243
|
+
return names;
|
|
244
|
+
};
|
|
245
|
+
function createRefWithViewNames(__typename, id, names) {
|
|
246
|
+
const ref = {
|
|
247
|
+
__typename,
|
|
248
|
+
id
|
|
249
|
+
};
|
|
250
|
+
assignViewTag(ref, names);
|
|
251
|
+
return Object.freeze(ref);
|
|
252
|
+
}
|
|
253
|
+
//#endregion
|
|
254
|
+
//#region src/selection.ts
|
|
255
|
+
const isConnectionSelection = (value) => isRecord(value.items) && "node" in value.items;
|
|
256
|
+
/**
|
|
257
|
+
* Flattens a view into a `SelectionPlan`, expanding composed views and
|
|
258
|
+
* partitioning nested args so the client can fetch exactly what is declared.
|
|
259
|
+
*/
|
|
260
|
+
const getSelectionPlan = (viewComposition, ref) => {
|
|
261
|
+
const args = /* @__PURE__ */ new Map();
|
|
262
|
+
const live = /* @__PURE__ */ new Map();
|
|
263
|
+
const paths = /* @__PURE__ */ new Set();
|
|
264
|
+
const assignArgs = (path, value, ignoreKeys) => {
|
|
265
|
+
const hash = hashArgs(value, { ignoreKeys });
|
|
266
|
+
args.set(path, {
|
|
267
|
+
hash,
|
|
268
|
+
ignoreKeys,
|
|
269
|
+
value
|
|
270
|
+
});
|
|
271
|
+
};
|
|
272
|
+
const walk = (selection, prefix, context = "default") => {
|
|
273
|
+
if (prefix === null && context !== "connection" && isConnectionSelection(selection)) {
|
|
274
|
+
if (selection.live && isRecord(selection.live)) live.set("", {
|
|
275
|
+
append: selection.live.append === "visible" ? "visible" : "edge",
|
|
276
|
+
prepend: selection.live.prepend === "visible" ? "visible" : "edge"
|
|
277
|
+
});
|
|
278
|
+
if (selection.args && isRecord(selection.args)) {
|
|
279
|
+
const clonedArgs = cloneArgs(selection.args, "args");
|
|
280
|
+
const ignoreKeys = isRecord(selection.items) && isRecord(selection.items.node) ? paginationArgKeys : void 0;
|
|
281
|
+
assignArgs("", clonedArgs, ignoreKeys);
|
|
282
|
+
}
|
|
283
|
+
const { args: _args, live: _live, ...withoutArgs } = selection;
|
|
284
|
+
walk(withoutArgs, prefix, "connection");
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
287
|
+
for (const [key, value] of Object.entries(selection)) {
|
|
288
|
+
const valueType = typeof value;
|
|
289
|
+
const path = prefix ? `${prefix}.${key}` : key;
|
|
290
|
+
if (context === "connection") {
|
|
291
|
+
if (key === "args" || key === "live" || key === "pagination") continue;
|
|
292
|
+
if (key === "items" && isRecord(value)) {
|
|
293
|
+
if (isRecord(value.node)) walk(value.node, prefix);
|
|
294
|
+
continue;
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
if (valueType === "boolean") {
|
|
298
|
+
if (value) paths.add(path);
|
|
299
|
+
continue;
|
|
300
|
+
}
|
|
301
|
+
if (isDeferredSelection(value)) continue;
|
|
302
|
+
if (isViewTag(key)) {
|
|
303
|
+
if (!ref || ref[ViewsTag] && ref[ViewsTag].has(key)) walk(value.select, prefix);
|
|
304
|
+
continue;
|
|
305
|
+
}
|
|
306
|
+
if (isRecord(value)) {
|
|
307
|
+
const selectionObject = value;
|
|
308
|
+
if (isConnectionSelection(selectionObject)) {
|
|
309
|
+
if (selectionObject.live && isRecord(selectionObject.live)) live.set(path, {
|
|
310
|
+
append: selectionObject.live.append === "visible" ? "visible" : "edge",
|
|
311
|
+
prepend: selectionObject.live.prepend === "visible" ? "visible" : "edge"
|
|
312
|
+
});
|
|
313
|
+
if (selectionObject.args && isRecord(selectionObject.args)) {
|
|
314
|
+
const clonedArgs = cloneArgs(selectionObject.args, path);
|
|
315
|
+
const ignoreKeys = isRecord(selectionObject.items) && isRecord(selectionObject.items.node) ? paginationArgKeys : void 0;
|
|
316
|
+
assignArgs(path, clonedArgs, ignoreKeys);
|
|
317
|
+
}
|
|
318
|
+
const { args: _ignored, live: _live, ...withoutArgs } = selectionObject;
|
|
319
|
+
walk(withoutArgs, path, "connection");
|
|
320
|
+
continue;
|
|
321
|
+
}
|
|
322
|
+
const hasArgs = selectionObject.args && isRecord(selectionObject.args);
|
|
323
|
+
let selectionWithoutArgs = selectionObject;
|
|
324
|
+
if (hasArgs) {
|
|
325
|
+
const clonedArgs = cloneArgs(selectionObject.args, path);
|
|
326
|
+
const ignoreKeys = isRecord(selectionObject.items) && isRecord(selectionObject.items?.node) ? paginationArgKeys : void 0;
|
|
327
|
+
assignArgs(path, clonedArgs, ignoreKeys);
|
|
328
|
+
const { args: _args, ...rest } = selectionObject;
|
|
329
|
+
selectionWithoutArgs = rest;
|
|
330
|
+
}
|
|
331
|
+
if (Object.keys(selectionWithoutArgs).length > 0) walk(selectionWithoutArgs, path);
|
|
332
|
+
else if (hasArgs) paths.add(path);
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
};
|
|
336
|
+
const payloads = getViewPayloads(viewComposition, ref);
|
|
337
|
+
if (payloads.length === 0 && isRecord(viewComposition)) walk(viewComposition, null);
|
|
338
|
+
else for (const payload of payloads) walk(payload.select, null);
|
|
339
|
+
return {
|
|
340
|
+
args,
|
|
341
|
+
live,
|
|
342
|
+
paths
|
|
343
|
+
};
|
|
344
|
+
};
|
|
345
|
+
const getDeferredSelectionPlan = (field, selection) => {
|
|
346
|
+
return getSelectionPlan({ [field]: selection }, null);
|
|
347
|
+
};
|
|
348
|
+
//#endregion
|
|
349
|
+
//#region src/mutation.ts
|
|
350
|
+
/**
|
|
351
|
+
* Defines a mutation for a given entity type, preserving the input and output
|
|
352
|
+
* types for transports.
|
|
353
|
+
*/
|
|
354
|
+
function mutation(entity) {
|
|
355
|
+
return Object.freeze({
|
|
356
|
+
entity,
|
|
357
|
+
[MutationKind]: true
|
|
358
|
+
});
|
|
359
|
+
}
|
|
360
|
+
const collectImplicitSelectedPaths = (value) => {
|
|
361
|
+
const paths = /* @__PURE__ */ new Set();
|
|
362
|
+
const walk = (current, prefix) => {
|
|
363
|
+
if (!current || typeof current !== "object") {
|
|
364
|
+
if (prefix) paths.add(prefix);
|
|
365
|
+
return;
|
|
366
|
+
}
|
|
367
|
+
if (Array.isArray(current)) {
|
|
368
|
+
if (prefix) paths.add(prefix);
|
|
369
|
+
for (const child of current) walk(child, prefix);
|
|
370
|
+
return;
|
|
371
|
+
}
|
|
372
|
+
for (const [key, child] of Object.entries(current)) {
|
|
373
|
+
const next = prefix ? `${prefix}.${key}` : key;
|
|
374
|
+
paths.add(next);
|
|
375
|
+
walk(child, next);
|
|
376
|
+
}
|
|
377
|
+
};
|
|
378
|
+
walk(value, null);
|
|
379
|
+
return paths;
|
|
380
|
+
};
|
|
381
|
+
const maybeGetId = (getId, input) => {
|
|
382
|
+
try {
|
|
383
|
+
return getId(input);
|
|
384
|
+
} catch {
|
|
385
|
+
return null;
|
|
386
|
+
}
|
|
387
|
+
};
|
|
388
|
+
const emptySet = /* @__PURE__ */ new Set();
|
|
389
|
+
/** @internal Creates the same optimistic/confirmation machinery for live and restored calls. */
|
|
390
|
+
function prepareMutation(client, command, config, durable = false) {
|
|
391
|
+
const { args, delete: deleteRecord, entity, input, insert, key, optimistic, plan } = command;
|
|
392
|
+
const id = maybeGetId(config.getId, input);
|
|
393
|
+
const optimisticRecord = optimistic ? id != null ? {
|
|
394
|
+
id,
|
|
395
|
+
...optimistic
|
|
396
|
+
} : optimistic : void 0;
|
|
397
|
+
const optimisticRecordId = optimisticRecord ? maybeGetId(config.getId, optimisticRecord) : null;
|
|
398
|
+
const optimisticEntityId = id != null ? toEntityId(entity, id) : optimisticRecordId != null ? toEntityId(entity, optimisticRecordId) : null;
|
|
399
|
+
const optimisticSelection = optimisticRecord ? collectImplicitSelectedPaths(optimisticRecord) : void 0;
|
|
400
|
+
const selection = /* @__PURE__ */ new Set([...plan?.paths ?? [], ...optimisticSelection ?? []]);
|
|
401
|
+
if (deleteRecord && id == null) throw new Error(`fate: Mutation '${key}' requires an 'id' to delete.`);
|
|
402
|
+
const settle = client.store.optimisticUpdate(() => {
|
|
403
|
+
if (optimisticRecord && optimisticEntityId) client.write(entity, optimisticRecord, optimisticSelection ?? emptySet, plan, insert);
|
|
404
|
+
if (deleteRecord && id != null) client.deleteRecord(entity, id);
|
|
405
|
+
}, durable);
|
|
406
|
+
return {
|
|
407
|
+
commit: (result) => settle(() => {
|
|
408
|
+
if (result && typeof result === "object" && (!deleteRecord || plan)) {
|
|
409
|
+
client.write(entity, result, collectImplicitSelectedPaths(result), plan, insert);
|
|
410
|
+
const resultId = maybeGetId(config.getId, result);
|
|
411
|
+
if (optimisticEntityId && resultId != null) client.resolveOptimisticEntity(optimisticEntityId, toEntityId(entity, resultId));
|
|
412
|
+
if (optimisticRecordId != null && resultId != null && optimisticRecordId !== resultId) client.deleteRecord(entity, optimisticRecordId);
|
|
413
|
+
}
|
|
414
|
+
if (deleteRecord && id != null) client.deleteRecord(entity, id);
|
|
415
|
+
}),
|
|
416
|
+
entityId: optimisticEntityId,
|
|
417
|
+
execute: (identity) => client.executeMutation(key, input, selection, {
|
|
418
|
+
args,
|
|
419
|
+
identity,
|
|
420
|
+
plan
|
|
421
|
+
}),
|
|
422
|
+
rollback: () => settle()
|
|
423
|
+
};
|
|
424
|
+
}
|
|
425
|
+
/** Binds a mutation's optimistic updates, cache writes, and error handling. */
|
|
426
|
+
function wrapMutation(client, identifier) {
|
|
427
|
+
const config = client.getTypeConfig(identifier.entity);
|
|
428
|
+
return async ({ args, delete: deleteRecord, input, insert = "after", optimistic, persist, view }) => {
|
|
429
|
+
const command = {
|
|
430
|
+
args,
|
|
431
|
+
delete: deleteRecord,
|
|
432
|
+
entity: identifier.entity,
|
|
433
|
+
input,
|
|
434
|
+
insert,
|
|
435
|
+
key: identifier.key,
|
|
436
|
+
optimistic,
|
|
437
|
+
plan: view ? getSelectionPlan(view, null) : void 0
|
|
438
|
+
};
|
|
439
|
+
if (client.persistence && persist !== false) try {
|
|
440
|
+
return {
|
|
441
|
+
error: void 0,
|
|
442
|
+
result: await client.withPersistenceLifecycle(() => client.persistence.mutate(command))
|
|
443
|
+
};
|
|
444
|
+
} catch (error) {
|
|
445
|
+
return handleMutationError(identifier.key, error);
|
|
446
|
+
}
|
|
447
|
+
if (client.persistence) await client.withPersistenceLifecycle(() => client.persistence.ready);
|
|
448
|
+
client.assertPersistenceActive();
|
|
449
|
+
const operation = prepareMutation(client, command, config);
|
|
450
|
+
const performMutation = async () => {
|
|
451
|
+
try {
|
|
452
|
+
const result = await operation.execute();
|
|
453
|
+
client.assertPersistenceActive();
|
|
454
|
+
operation.commit(result);
|
|
455
|
+
return {
|
|
456
|
+
error: void 0,
|
|
457
|
+
result
|
|
458
|
+
};
|
|
459
|
+
} catch (error) {
|
|
460
|
+
operation.rollback();
|
|
461
|
+
return handleMutationError(identifier.key, error);
|
|
462
|
+
}
|
|
463
|
+
};
|
|
464
|
+
const mutationPromise = client.trackPendingRequest(performMutation);
|
|
465
|
+
if (operation.entityId) client.registerPendingOptimisticMutation(operation.entityId, mutationPromise);
|
|
466
|
+
return mutationPromise;
|
|
467
|
+
};
|
|
468
|
+
}
|
|
469
|
+
const handleMutationError = (key, error) => {
|
|
470
|
+
if (error instanceof Error) {
|
|
471
|
+
const statusCode = getErrorStatusCode(error);
|
|
472
|
+
if ((statusCode ? categorizeHTTPErrorStatus(statusCode) : "boundary") === "boundary") throw error;
|
|
473
|
+
return {
|
|
474
|
+
error,
|
|
475
|
+
result: void 0
|
|
476
|
+
};
|
|
477
|
+
}
|
|
478
|
+
throw new Error(`fate: Mutation '${key}' failed.`, { cause: error });
|
|
479
|
+
};
|
|
480
|
+
const getErrorStatusCode = (error) => {
|
|
481
|
+
if (error instanceof FateRequestError) return error.status;
|
|
482
|
+
const { data } = error;
|
|
483
|
+
if (!data || typeof data !== "object") return;
|
|
484
|
+
return getHTTPStatusCodeFromError(data);
|
|
485
|
+
};
|
|
486
|
+
const categorizeHTTPErrorStatus = (statusCode) => {
|
|
487
|
+
switch (statusCode) {
|
|
488
|
+
case 400:
|
|
489
|
+
case 402:
|
|
490
|
+
case 404:
|
|
491
|
+
case 408:
|
|
492
|
+
case 409:
|
|
493
|
+
case 412:
|
|
494
|
+
case 413:
|
|
495
|
+
case 415:
|
|
496
|
+
case 422:
|
|
497
|
+
case 429:
|
|
498
|
+
case 499: return "callSite";
|
|
499
|
+
default: return "boundary";
|
|
500
|
+
}
|
|
501
|
+
};
|
|
502
|
+
//#endregion
|
|
503
|
+
//#region src/mask.ts
|
|
504
|
+
function emptyMask() {
|
|
505
|
+
return {
|
|
506
|
+
all: false,
|
|
507
|
+
children: /* @__PURE__ */ new Map()
|
|
508
|
+
};
|
|
509
|
+
}
|
|
510
|
+
function cloneMask(m) {
|
|
511
|
+
const clone = {
|
|
512
|
+
all: m.all,
|
|
513
|
+
children: /* @__PURE__ */ new Map()
|
|
514
|
+
};
|
|
515
|
+
for (const [key, value] of m.children) clone.children.set(key, cloneMask(value));
|
|
516
|
+
return clone;
|
|
517
|
+
}
|
|
518
|
+
function addPath(mask, path) {
|
|
519
|
+
if (mask.all) return;
|
|
520
|
+
const parts = path.split(".");
|
|
521
|
+
let current = mask;
|
|
522
|
+
for (let i = 0; i < parts.length; i++) {
|
|
523
|
+
const seg = parts[i];
|
|
524
|
+
let child = current.children.get(seg);
|
|
525
|
+
if (!child) {
|
|
526
|
+
child = emptyMask();
|
|
527
|
+
current.children.set(seg, child);
|
|
528
|
+
}
|
|
529
|
+
current = child;
|
|
530
|
+
}
|
|
531
|
+
current.all = true;
|
|
532
|
+
current.children.clear();
|
|
533
|
+
}
|
|
534
|
+
function union(into, b) {
|
|
535
|
+
if (into.all || b.all) {
|
|
536
|
+
into.all = true;
|
|
537
|
+
into.children.clear();
|
|
538
|
+
return;
|
|
539
|
+
}
|
|
540
|
+
for (const [key, child] of b.children) {
|
|
541
|
+
const exist = into.children.get(key);
|
|
542
|
+
if (!exist) into.children.set(key, cloneMask(child));
|
|
543
|
+
else union(exist, child);
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
function fromPaths(paths) {
|
|
547
|
+
const mask = emptyMask();
|
|
548
|
+
for (const path of paths) addPath(mask, path);
|
|
549
|
+
return mask;
|
|
550
|
+
}
|
|
551
|
+
function toPaths(mask) {
|
|
552
|
+
const paths = [];
|
|
553
|
+
const visit = (current, prefix) => {
|
|
554
|
+
if (current.all) {
|
|
555
|
+
if (prefix) paths.push(prefix);
|
|
556
|
+
return;
|
|
557
|
+
}
|
|
558
|
+
for (const [key, child] of current.children) visit(child, prefix ? `${prefix}.${key}` : key);
|
|
559
|
+
};
|
|
560
|
+
visit(mask, null);
|
|
561
|
+
return paths;
|
|
562
|
+
}
|
|
563
|
+
function isCovered(mask, path) {
|
|
564
|
+
if (mask.all) return true;
|
|
565
|
+
const parts = path.split(".");
|
|
566
|
+
let current = mask;
|
|
567
|
+
for (let i = 0; i < parts.length; i++) {
|
|
568
|
+
if (!current) return false;
|
|
569
|
+
if (current.all) return true;
|
|
570
|
+
current = current.children.get(parts[i]);
|
|
571
|
+
}
|
|
572
|
+
return !!current && (current.all || current.children.size === 0);
|
|
573
|
+
}
|
|
574
|
+
function diffPaths(paths, mask) {
|
|
575
|
+
const missing = /* @__PURE__ */ new Set();
|
|
576
|
+
for (const path of paths) if (!isCovered(mask, path)) missing.add(path);
|
|
577
|
+
return missing;
|
|
578
|
+
}
|
|
579
|
+
function intersects(a, b) {
|
|
580
|
+
if (a.all || b.all) return true;
|
|
581
|
+
for (const [key, child] of a.children) {
|
|
582
|
+
const otherChild = b.children.get(key);
|
|
583
|
+
if (!otherChild) continue;
|
|
584
|
+
if (child.all || otherChild.all) return true;
|
|
585
|
+
if (intersects(child, otherChild)) return true;
|
|
586
|
+
}
|
|
587
|
+
return false;
|
|
588
|
+
}
|
|
589
|
+
//#endregion
|
|
590
|
+
//#region src/store.ts
|
|
591
|
+
const listKeySeparator = " __fate__ ";
|
|
592
|
+
const decodeListKeyPart = (part) => {
|
|
593
|
+
try {
|
|
594
|
+
return decodeURIComponent(part);
|
|
595
|
+
} catch {
|
|
596
|
+
return part;
|
|
597
|
+
}
|
|
598
|
+
};
|
|
599
|
+
const encodeListKeyPart = (part) => encodeURIComponent(part);
|
|
600
|
+
const getListKey = (ownerId, field, hash = "default") => `${encodeListKeyPart(ownerId)}${listKeySeparator}${encodeListKeyPart(field)}${listKeySeparator}${encodeListKeyPart(hash)}`;
|
|
601
|
+
const getOwnerFieldKey = (ownerId, field) => JSON.stringify([ownerId, field]);
|
|
602
|
+
const parseListKey = (key) => {
|
|
603
|
+
const parts = key.split(listKeySeparator);
|
|
604
|
+
if (parts.length !== 3) return null;
|
|
605
|
+
const [ownerId, field] = parts;
|
|
606
|
+
return {
|
|
607
|
+
field: decodeListKeyPart(field),
|
|
608
|
+
ownerId: decodeListKeyPart(ownerId)
|
|
609
|
+
};
|
|
610
|
+
};
|
|
611
|
+
const isPlainRecord = (value) => {
|
|
612
|
+
if (value == null || typeof value !== "object" || Array.isArray(value) || isNodeRef(value)) return false;
|
|
613
|
+
const prototype = Object.getPrototypeOf(value);
|
|
614
|
+
return prototype === Object.prototype || prototype === null;
|
|
615
|
+
};
|
|
616
|
+
const isDate = (value) => value != null && typeof value === "object" && Object.prototype.toString.call(value) === "[object Date]" && typeof value.getTime === "function";
|
|
617
|
+
const mergePreservingExisting = (incoming, existing) => {
|
|
618
|
+
if (!isPlainRecord(incoming) || !isPlainRecord(existing)) return existing;
|
|
619
|
+
const result = Object.create(null);
|
|
620
|
+
for (const [key, value] of Object.entries(incoming)) Object.defineProperty(result, key, {
|
|
621
|
+
configurable: true,
|
|
622
|
+
enumerable: true,
|
|
623
|
+
value,
|
|
624
|
+
writable: true
|
|
625
|
+
});
|
|
626
|
+
for (const [key, value] of Object.entries(existing)) Object.defineProperty(result, key, {
|
|
627
|
+
configurable: true,
|
|
628
|
+
enumerable: true,
|
|
629
|
+
value: Object.hasOwn(result, key) ? mergePreservingExisting(result[key], value) : value,
|
|
630
|
+
writable: true
|
|
631
|
+
});
|
|
632
|
+
return result;
|
|
633
|
+
};
|
|
634
|
+
const areHydrationValuesEqual = (left, right) => {
|
|
635
|
+
if (Object.is(left, right)) return true;
|
|
636
|
+
if (isNodeRef(left) || isNodeRef(right)) return isNodeRef(left) && isNodeRef(right) && getNodeRefId(left) === getNodeRefId(right);
|
|
637
|
+
if (isDate(left) || isDate(right)) return isDate(left) && isDate(right) && left.getTime() === right.getTime();
|
|
638
|
+
if (Array.isArray(left) || Array.isArray(right)) return Array.isArray(left) && Array.isArray(right) && left.length === right.length && left.every((value, index) => areHydrationValuesEqual(value, right[index]));
|
|
639
|
+
if (!isPlainRecord(left) || !isPlainRecord(right)) return false;
|
|
640
|
+
const leftKeys = Object.keys(left);
|
|
641
|
+
const rightKeys = Object.keys(right);
|
|
642
|
+
return leftKeys.length === rightKeys.length && leftKeys.every((key) => Object.hasOwn(right, key) && areHydrationValuesEqual(left[key], right[key]));
|
|
643
|
+
};
|
|
644
|
+
const areMasksEqual = (left, right) => {
|
|
645
|
+
if (left === right || left?.all && right?.all) return true;
|
|
646
|
+
if (!left || !right || left.all !== right.all || left.children.size !== right.children.size) return false;
|
|
647
|
+
for (const [key, child] of left.children) if (!areMasksEqual(child, right.children.get(key))) return false;
|
|
648
|
+
return true;
|
|
649
|
+
};
|
|
650
|
+
const emptyFunction = () => {};
|
|
651
|
+
const hasReferences = (value) => isNodeRef(value) || Array.isArray(value) && value.some(isNodeRef);
|
|
652
|
+
const areNormalizedValuesEqual = (left, right) => Object.is(left, right) || isNodeRef(left) && isNodeRef(right) && getNodeRefId(left) === getNodeRefId(right) || Array.isArray(left) && Array.isArray(right) && left.length === right.length && left.every((value, index) => areNormalizedValuesEqual(value, right[index]));
|
|
653
|
+
var Store = class {
|
|
654
|
+
constructor(onRebase, onChange) {
|
|
655
|
+
this.onRebase = onRebase;
|
|
656
|
+
this.onChange = onChange;
|
|
657
|
+
this.coverage = /* @__PURE__ */ new Map();
|
|
658
|
+
this.listKeysByOwnerField = /* @__PURE__ */ new Map();
|
|
659
|
+
this.listKeysByReferencedEntity = /* @__PURE__ */ new Map();
|
|
660
|
+
this.lists = /* @__PURE__ */ new Map();
|
|
661
|
+
this.recordReferenceFields = /* @__PURE__ */ new Map();
|
|
662
|
+
this.recordReferencesByTarget = /* @__PURE__ */ new Map();
|
|
663
|
+
this.records = /* @__PURE__ */ new Map();
|
|
664
|
+
this.subscriptions = /* @__PURE__ */ new Map();
|
|
665
|
+
this.listSubscriptions = /* @__PURE__ */ new Map();
|
|
666
|
+
this.optimisticLayers = /* @__PURE__ */ new Set();
|
|
667
|
+
this.optimisticBase = {
|
|
668
|
+
lists: /* @__PURE__ */ new Map(),
|
|
669
|
+
records: /* @__PURE__ */ new Map()
|
|
670
|
+
};
|
|
671
|
+
}
|
|
672
|
+
get hasOptimisticUpdates() {
|
|
673
|
+
return this.optimisticLayers.size > 0;
|
|
674
|
+
}
|
|
675
|
+
get isRebasing() {
|
|
676
|
+
return this.rebase !== void 0;
|
|
677
|
+
}
|
|
678
|
+
/** Apply a synchronous cache update beneath the pending optimistic layers. */
|
|
679
|
+
update(apply) {
|
|
680
|
+
if (this.rebase || this.optimisticLayers.size === 0) return apply();
|
|
681
|
+
const rebase = {
|
|
682
|
+
lists: /* @__PURE__ */ new Map(),
|
|
683
|
+
records: /* @__PURE__ */ new Map()
|
|
684
|
+
};
|
|
685
|
+
this.rebase = rebase;
|
|
686
|
+
try {
|
|
687
|
+
for (const [id, snapshot] of this.optimisticBase.records) this.restore(id, snapshot);
|
|
688
|
+
for (const [key, list] of this.optimisticBase.lists) this.restoreList(key, list);
|
|
689
|
+
this.optimisticBase.records.clear();
|
|
690
|
+
this.optimisticBase.lists.clear();
|
|
691
|
+
return apply();
|
|
692
|
+
} finally {
|
|
693
|
+
try {
|
|
694
|
+
for (const layer of this.optimisticLayers) {
|
|
695
|
+
layer.writes.clear();
|
|
696
|
+
this.recordingLayer = layer;
|
|
697
|
+
layer.apply();
|
|
698
|
+
}
|
|
699
|
+
} finally {
|
|
700
|
+
this.recordingLayer = void 0;
|
|
701
|
+
const records = this.getRebaseChanges(rebase);
|
|
702
|
+
const lists = /* @__PURE__ */ new Set();
|
|
703
|
+
for (const [key, before] of rebase.lists) if (!areHydrationValuesEqual(before, this.lists.get(key))) {
|
|
704
|
+
lists.add(key);
|
|
705
|
+
const owner = parseListKey(key);
|
|
706
|
+
if (owner) {
|
|
707
|
+
if (!records.has(owner.ownerId)) records.set(owner.ownerId, /* @__PURE__ */ new Set([owner.field]));
|
|
708
|
+
else records.get(owner.ownerId)?.add(owner.field);
|
|
709
|
+
}
|
|
710
|
+
} else if (before) this.lists.set(key, before);
|
|
711
|
+
this.rebase = void 0;
|
|
712
|
+
this.onRebase?.(new Set(records.keys()));
|
|
713
|
+
for (const [id, paths] of records) this.notify(id, paths, false);
|
|
714
|
+
for (const key of lists) this.notifyListSubscribers(key, false);
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
getRebaseChanges(rebase) {
|
|
719
|
+
const changes = /* @__PURE__ */ new Map();
|
|
720
|
+
for (const [id, before] of rebase.records) {
|
|
721
|
+
const after = this.records.get(id);
|
|
722
|
+
const mask = this.coverage.get(id);
|
|
723
|
+
if (Boolean(before.record) !== Boolean(after) || before.mask?.all !== mask?.all) {
|
|
724
|
+
changes.set(id, void 0);
|
|
725
|
+
continue;
|
|
726
|
+
}
|
|
727
|
+
const paths = /* @__PURE__ */ new Set();
|
|
728
|
+
for (const key of /* @__PURE__ */ new Set([...Object.keys(before.record ?? {}), ...Object.keys(after ?? {})])) if (Object.hasOwn(before.record ?? {}, key) !== Object.hasOwn(after ?? {}, key) || !areNormalizedValuesEqual(before.record?.[key], after?.[key])) paths.add(key);
|
|
729
|
+
if (!areMasksEqual(before.mask, mask)) {
|
|
730
|
+
for (const path of before.mask ? toPaths(before.mask) : []) if (!mask || !isCovered(mask, path)) paths.add(path);
|
|
731
|
+
for (const path of mask ? toPaths(mask) : []) if (!before.mask || !isCovered(before.mask, path)) paths.add(path);
|
|
732
|
+
}
|
|
733
|
+
if (paths.size > 0) changes.set(id, paths);
|
|
734
|
+
else if (before.record) this.records.set(id, before.record);
|
|
735
|
+
}
|
|
736
|
+
return changes;
|
|
737
|
+
}
|
|
738
|
+
/** Returns an idempotent settlement function; omit its update to roll back. */
|
|
739
|
+
optimisticUpdate(apply, durable = false) {
|
|
740
|
+
const layer = {
|
|
741
|
+
apply,
|
|
742
|
+
durable,
|
|
743
|
+
writes: /* @__PURE__ */ new Map()
|
|
744
|
+
};
|
|
745
|
+
const settle = (commit) => {
|
|
746
|
+
if (!this.optimisticLayers.has(layer) || layer.settled) return;
|
|
747
|
+
this.update(() => {
|
|
748
|
+
const overlays = commit ? this.getPendingOverlap(layer, layer.writes) : /* @__PURE__ */ new Map();
|
|
749
|
+
if (overlays.size > 0) {
|
|
750
|
+
layer.settled = true;
|
|
751
|
+
layer.apply = () => {
|
|
752
|
+
for (const [id, { partial, paths }] of this.getPendingOverlap(layer, overlays)) this.merge(id, partial, paths);
|
|
753
|
+
};
|
|
754
|
+
} else this.optimisticLayers.delete(layer);
|
|
755
|
+
for (const pending of this.optimisticLayers) {
|
|
756
|
+
if (!pending.settled) break;
|
|
757
|
+
this.optimisticLayers.delete(pending);
|
|
758
|
+
}
|
|
759
|
+
commit?.();
|
|
760
|
+
});
|
|
761
|
+
};
|
|
762
|
+
this.optimisticLayers.add(layer);
|
|
763
|
+
try {
|
|
764
|
+
this.update(() => {});
|
|
765
|
+
} catch (error) {
|
|
766
|
+
settle();
|
|
767
|
+
throw error;
|
|
768
|
+
}
|
|
769
|
+
return settle;
|
|
770
|
+
}
|
|
771
|
+
getPendingOverlap(layer, writes) {
|
|
772
|
+
const earlier = [];
|
|
773
|
+
for (const pending of this.optimisticLayers) {
|
|
774
|
+
if (pending === layer) break;
|
|
775
|
+
if (!pending.settled) earlier.push(pending);
|
|
776
|
+
}
|
|
777
|
+
const overlaps = /* @__PURE__ */ new Map();
|
|
778
|
+
for (const [id, write] of writes) {
|
|
779
|
+
const partial = Object.fromEntries(Object.entries(write.partial).filter(([key, value]) => !hasReferences(value) && earlier.some(({ writes }) => {
|
|
780
|
+
const previous = writes.get(id)?.partial;
|
|
781
|
+
return previous && Object.hasOwn(previous, key) && !hasReferences(previous[key]);
|
|
782
|
+
})));
|
|
783
|
+
if (Object.keys(partial).length) overlaps.set(id, {
|
|
784
|
+
partial,
|
|
785
|
+
paths: new Set([...write.paths].filter((path) => Object.hasOwn(partial, path.split(".")[0])))
|
|
786
|
+
});
|
|
787
|
+
}
|
|
788
|
+
return overlaps;
|
|
789
|
+
}
|
|
790
|
+
captureRecord(id) {
|
|
791
|
+
if (!this.rebase) return;
|
|
792
|
+
for (const target of [this.rebase, this.recordingLayer && this.optimisticBase]) if (target && !target.records.has(id)) {
|
|
793
|
+
const mask = this.coverage.get(id);
|
|
794
|
+
target.records.set(id, {
|
|
795
|
+
mask: mask ? cloneMask(mask) : void 0,
|
|
796
|
+
record: this.records.get(id)
|
|
797
|
+
});
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
captureList(key) {
|
|
801
|
+
if (!this.rebase) return;
|
|
802
|
+
for (const target of [this.rebase, this.recordingLayer && this.optimisticBase]) if (target && !target.lists.has(key)) target.lists.set(key, this.lists.get(key));
|
|
803
|
+
}
|
|
804
|
+
get hasTransientOptimisticUpdates() {
|
|
805
|
+
return [...this.optimisticLayers].some((layer) => !layer.durable);
|
|
806
|
+
}
|
|
807
|
+
getOptimisticRoots() {
|
|
808
|
+
return {
|
|
809
|
+
baseRecords: [...this.optimisticBase.records.values()].flatMap(({ record }) => record ? [record] : []),
|
|
810
|
+
lists: [...this.optimisticBase.lists.keys()],
|
|
811
|
+
records: [...this.optimisticBase.records.keys(), ...[...this.optimisticBase.lists.values()].flatMap((list) => list ? [
|
|
812
|
+
...list.ids,
|
|
813
|
+
...list.pendingBeforeIds ?? [],
|
|
814
|
+
...list.pendingAfterIds ?? [],
|
|
815
|
+
...list.liveBeforeIds ?? [],
|
|
816
|
+
...list.liveAfterIds ?? []
|
|
817
|
+
] : [])]
|
|
818
|
+
};
|
|
819
|
+
}
|
|
820
|
+
/** @internal Indexed owners needed for mutation rollback. */
|
|
821
|
+
getPersistenceOwners(id) {
|
|
822
|
+
return {
|
|
823
|
+
lists: [...this.listKeysByReferencedEntity.get(id) ?? []],
|
|
824
|
+
records: [...this.recordReferencesByTarget.get(id)?.keys() ?? []]
|
|
825
|
+
};
|
|
826
|
+
}
|
|
827
|
+
/** @internal Read one confirmed record without copying the cache. */
|
|
828
|
+
readConfirmed(id) {
|
|
829
|
+
const snapshot = this.optimisticBase.records.get(id);
|
|
830
|
+
const record = snapshot ? snapshot.record : this.records.get(id);
|
|
831
|
+
const mask = snapshot ? snapshot.mask : this.coverage.get(id);
|
|
832
|
+
return record ? {
|
|
833
|
+
paths: mask ? toPaths(mask) : [],
|
|
834
|
+
record
|
|
835
|
+
} : void 0;
|
|
836
|
+
}
|
|
837
|
+
/** @internal */
|
|
838
|
+
readConfirmedList(key) {
|
|
839
|
+
return this.optimisticBase.lists.has(key) ? this.optimisticBase.lists.get(key) : this.lists.get(key);
|
|
840
|
+
}
|
|
841
|
+
dehydrateConfirmed() {
|
|
842
|
+
const records = new Map(this.records);
|
|
843
|
+
const coverage = new Map(this.coverage);
|
|
844
|
+
const lists = new Map(this.lists);
|
|
845
|
+
for (const [id, snapshot] of this.optimisticBase.records) {
|
|
846
|
+
if (snapshot.record === void 0) records.delete(id);
|
|
847
|
+
else records.set(id, snapshot.record);
|
|
848
|
+
if (snapshot.mask === void 0) coverage.delete(id);
|
|
849
|
+
else coverage.set(id, snapshot.mask);
|
|
850
|
+
}
|
|
851
|
+
for (const [key, list] of this.optimisticBase.lists) if (list === void 0) lists.delete(key);
|
|
852
|
+
else lists.set(key, list);
|
|
853
|
+
return {
|
|
854
|
+
coverage: [...coverage].map(([id, mask]) => [id, toPaths(mask)]),
|
|
855
|
+
lists: [...lists],
|
|
856
|
+
records: [...records]
|
|
857
|
+
};
|
|
858
|
+
}
|
|
859
|
+
dehydrate() {
|
|
860
|
+
return {
|
|
861
|
+
coverage: [...this.coverage].map(([id, mask]) => [id, toPaths(mask)]),
|
|
862
|
+
lists: [...this.lists],
|
|
863
|
+
records: [...this.records]
|
|
864
|
+
};
|
|
865
|
+
}
|
|
866
|
+
hydrate(state, mode, options = {}) {
|
|
867
|
+
const previousCoverage = this.coverage;
|
|
868
|
+
const previousLists = this.lists;
|
|
869
|
+
const previousRecords = this.records;
|
|
870
|
+
const coverage = mode === "replace" ? /* @__PURE__ */ new Map() : new Map([...previousCoverage].map(([id, mask]) => [id, cloneMask(mask)]));
|
|
871
|
+
const lists = mode === "replace" ? /* @__PURE__ */ new Map() : new Map(previousLists);
|
|
872
|
+
const records = mode === "replace" ? /* @__PURE__ */ new Map() : new Map(previousRecords);
|
|
873
|
+
for (const [id, incoming] of state.records) {
|
|
874
|
+
const previous = records.get(id);
|
|
875
|
+
const next = previous && mode === "preserve-existing" ? mergePreservingExisting(incoming, previous) : incoming;
|
|
876
|
+
records.set(id, previous && areHydrationValuesEqual(previous, next) ? previous : next);
|
|
877
|
+
}
|
|
878
|
+
for (const [id, paths] of state.coverage) {
|
|
879
|
+
const incoming = fromPaths(paths);
|
|
880
|
+
const previous = coverage.get(id);
|
|
881
|
+
if (previous && mode === "preserve-existing") union(previous, incoming);
|
|
882
|
+
else coverage.set(id, incoming);
|
|
883
|
+
}
|
|
884
|
+
for (const [key, list] of state.lists) {
|
|
885
|
+
const previous = lists.get(key);
|
|
886
|
+
if (mode === "replace" || !previous) lists.set(key, previous && areHydrationValuesEqual(previous, list) ? previous : list);
|
|
887
|
+
}
|
|
888
|
+
this.coverage = coverage;
|
|
889
|
+
this.lists = lists;
|
|
890
|
+
this.records = records;
|
|
891
|
+
this.rebuildIndexes();
|
|
892
|
+
const changedRecordIds = /* @__PURE__ */ new Set();
|
|
893
|
+
for (const id of /* @__PURE__ */ new Set([...previousRecords.keys(), ...records.keys()])) if (!areHydrationValuesEqual(previousRecords.get(id), records.get(id)) || !areMasksEqual(previousCoverage.get(id), coverage.get(id))) changedRecordIds.add(id);
|
|
894
|
+
const changedListKeys = /* @__PURE__ */ new Set();
|
|
895
|
+
for (const key of /* @__PURE__ */ new Set([...previousLists.keys(), ...lists.keys()])) if (!areHydrationValuesEqual(previousLists.get(key), lists.get(key))) changedListKeys.add(key);
|
|
896
|
+
const notify = () => {
|
|
897
|
+
for (const id of changedRecordIds) this.notify(id);
|
|
898
|
+
for (const key of changedListKeys) this.notifyListSubscribers(key);
|
|
899
|
+
};
|
|
900
|
+
if (!options.deferNotifications) notify();
|
|
901
|
+
return notify;
|
|
902
|
+
}
|
|
903
|
+
rebuildIndexes() {
|
|
904
|
+
this.listKeysByOwnerField.clear();
|
|
905
|
+
this.listKeysByReferencedEntity.clear();
|
|
906
|
+
this.recordReferenceFields.clear();
|
|
907
|
+
this.recordReferencesByTarget.clear();
|
|
908
|
+
for (const [id, record] of this.records) this.addRecordReferenceIndexes(id, record);
|
|
909
|
+
for (const [key, list] of this.lists) this.addListIndexes(key, list);
|
|
910
|
+
}
|
|
911
|
+
read(id) {
|
|
912
|
+
return this.records.get(id);
|
|
913
|
+
}
|
|
914
|
+
merge(id, partial, paths) {
|
|
915
|
+
return this.update(() => {
|
|
916
|
+
const changedPaths = this.mergeInternal(id, partial, paths);
|
|
917
|
+
if (changedPaths) this.notify(id, changedPaths);
|
|
918
|
+
});
|
|
919
|
+
}
|
|
920
|
+
mergeInternal(id, partial, paths) {
|
|
921
|
+
this.captureRecord(id);
|
|
922
|
+
const selectedPaths = this.recordingLayer ? new Set(paths) : paths;
|
|
923
|
+
if (this.recordingLayer) {
|
|
924
|
+
const write = this.recordingLayer.writes.get(id) ?? {
|
|
925
|
+
partial: {},
|
|
926
|
+
paths: /* @__PURE__ */ new Set()
|
|
927
|
+
};
|
|
928
|
+
Object.assign(write.partial, partial);
|
|
929
|
+
for (const path of selectedPaths) write.paths.add(path);
|
|
930
|
+
this.recordingLayer.writes.set(id, write);
|
|
931
|
+
}
|
|
932
|
+
const previous = this.records.get(id);
|
|
933
|
+
const changedPaths = /* @__PURE__ */ new Set();
|
|
934
|
+
let mask = this.coverage.get(id);
|
|
935
|
+
if (!mask) {
|
|
936
|
+
mask = emptyMask();
|
|
937
|
+
this.coverage.set(id, mask);
|
|
938
|
+
}
|
|
939
|
+
union(mask, fromPaths(selectedPaths));
|
|
940
|
+
if (previous) {
|
|
941
|
+
let hasChanges = false;
|
|
942
|
+
for (const [key, value] of Object.entries(partial)) if (previous[key] !== value) {
|
|
943
|
+
hasChanges = true;
|
|
944
|
+
changedPaths.add(key);
|
|
945
|
+
}
|
|
946
|
+
if (!hasChanges) return null;
|
|
947
|
+
const nextRecord = {
|
|
948
|
+
...previous,
|
|
949
|
+
...partial
|
|
950
|
+
};
|
|
951
|
+
this.records.set(id, nextRecord);
|
|
952
|
+
this.updateRecordReferenceIndexes(id, previous, nextRecord, changedPaths);
|
|
953
|
+
} else {
|
|
954
|
+
const nextRecord = { ...partial };
|
|
955
|
+
this.records.set(id, nextRecord);
|
|
956
|
+
this.updateRecordReferenceIndexes(id, void 0, nextRecord, Object.keys(nextRecord));
|
|
957
|
+
}
|
|
958
|
+
return changedPaths;
|
|
959
|
+
}
|
|
960
|
+
deleteRecord(id) {
|
|
961
|
+
return this.update(() => {
|
|
962
|
+
this.captureRecord(id);
|
|
963
|
+
const record = this.records.get(id);
|
|
964
|
+
if (record) this.removeRecordReferenceIndexes(id, record);
|
|
965
|
+
this.records.delete(id);
|
|
966
|
+
this.coverage.delete(id);
|
|
967
|
+
if (!this.recordingLayer) this.onChange?.("record", id);
|
|
968
|
+
});
|
|
969
|
+
}
|
|
970
|
+
missingForSelection(id, paths) {
|
|
971
|
+
const requested = new Set(paths);
|
|
972
|
+
if (!this.records.has(id)) return requested;
|
|
973
|
+
const mask = this.coverage.get(id);
|
|
974
|
+
if (!mask) return requested;
|
|
975
|
+
return diffPaths(requested, mask);
|
|
976
|
+
}
|
|
977
|
+
subscribe(id, selectionOrFn, callback) {
|
|
978
|
+
let mask = null;
|
|
979
|
+
let fn = emptyFunction;
|
|
980
|
+
if (typeof selectionOrFn === "function") fn = selectionOrFn;
|
|
981
|
+
else if (callback) {
|
|
982
|
+
mask = selectionOrFn ? fromPaths(selectionOrFn) : null;
|
|
983
|
+
fn = callback;
|
|
984
|
+
}
|
|
985
|
+
let subscribers = this.subscriptions.get(id);
|
|
986
|
+
if (!subscribers) {
|
|
987
|
+
subscribers = /* @__PURE__ */ new Set();
|
|
988
|
+
this.subscriptions.set(id, subscribers);
|
|
989
|
+
}
|
|
990
|
+
const subscription = {
|
|
991
|
+
fn,
|
|
992
|
+
mask
|
|
993
|
+
};
|
|
994
|
+
subscribers.add(subscription);
|
|
995
|
+
return () => {
|
|
996
|
+
const set = this.subscriptions.get(id);
|
|
997
|
+
if (!set) return;
|
|
998
|
+
set.delete(subscription);
|
|
999
|
+
if (set.size === 0) this.subscriptions.delete(id);
|
|
1000
|
+
};
|
|
1001
|
+
}
|
|
1002
|
+
notify(id, paths, confirmed = true) {
|
|
1003
|
+
if (confirmed && !this.recordingLayer) this.onChange?.("record", id, paths);
|
|
1004
|
+
if (this.rebase) return;
|
|
1005
|
+
const set = this.subscriptions.get(id);
|
|
1006
|
+
if (!set) return;
|
|
1007
|
+
const changedPaths = paths ? [...paths] : [];
|
|
1008
|
+
const changedMask = changedPaths.length > 0 ? fromPaths(changedPaths) : null;
|
|
1009
|
+
for (const { fn, mask } of set) {
|
|
1010
|
+
if (mask && changedMask && !intersects(changedMask, mask)) continue;
|
|
1011
|
+
try {
|
|
1012
|
+
fn();
|
|
1013
|
+
} catch {}
|
|
1014
|
+
}
|
|
1015
|
+
}
|
|
1016
|
+
notifyListSubscribers(key, confirmed = true, previousList) {
|
|
1017
|
+
if (confirmed && !this.recordingLayer) this.onChange?.("list", key, void 0, previousList);
|
|
1018
|
+
if (this.rebase) return;
|
|
1019
|
+
const set = this.listSubscriptions.get(key);
|
|
1020
|
+
if (!set) return;
|
|
1021
|
+
for (const fn of set) try {
|
|
1022
|
+
fn();
|
|
1023
|
+
} catch {}
|
|
1024
|
+
}
|
|
1025
|
+
getList(key) {
|
|
1026
|
+
return this.lists.get(key)?.ids;
|
|
1027
|
+
}
|
|
1028
|
+
getListState(key) {
|
|
1029
|
+
return this.lists.get(key);
|
|
1030
|
+
}
|
|
1031
|
+
getListsForField(ownerId, field) {
|
|
1032
|
+
const entries = [];
|
|
1033
|
+
const keys = this.listKeysByOwnerField.get(getOwnerFieldKey(ownerId, field));
|
|
1034
|
+
if (!keys) return entries;
|
|
1035
|
+
for (const key of keys) {
|
|
1036
|
+
const list = this.lists.get(key);
|
|
1037
|
+
if (list) entries.push([key, list]);
|
|
1038
|
+
}
|
|
1039
|
+
return entries;
|
|
1040
|
+
}
|
|
1041
|
+
setList(key, state) {
|
|
1042
|
+
return this.update(() => {
|
|
1043
|
+
this.captureList(key);
|
|
1044
|
+
const previous = this.lists.get(key);
|
|
1045
|
+
if (previous) this.removeListIndexes(key, previous);
|
|
1046
|
+
this.lists.set(key, state);
|
|
1047
|
+
this.addListIndexes(key, state);
|
|
1048
|
+
this.notifyListSubscribers(key, true, previous);
|
|
1049
|
+
});
|
|
1050
|
+
}
|
|
1051
|
+
replaceListEntityId(previousId, nextId) {
|
|
1052
|
+
return this.update(() => {
|
|
1053
|
+
const keys = [...this.listKeysByReferencedEntity.get(previousId) ?? []];
|
|
1054
|
+
for (const key of keys) {
|
|
1055
|
+
const list = this.lists.get(key);
|
|
1056
|
+
if (!list) continue;
|
|
1057
|
+
let changed = false;
|
|
1058
|
+
let ids = list.ids;
|
|
1059
|
+
let cursors = list.cursors;
|
|
1060
|
+
if (list.ids.includes(previousId)) {
|
|
1061
|
+
const nextIds = [];
|
|
1062
|
+
const nextCursors = list.cursors ? [] : void 0;
|
|
1063
|
+
const seenIds = /* @__PURE__ */ new Set();
|
|
1064
|
+
list.ids.forEach((id, index) => {
|
|
1065
|
+
const resolved = id === previousId ? nextId : id;
|
|
1066
|
+
if (seenIds.has(resolved)) return;
|
|
1067
|
+
seenIds.add(resolved);
|
|
1068
|
+
nextIds.push(resolved);
|
|
1069
|
+
if (nextCursors) nextCursors.push(list.cursors?.[index]);
|
|
1070
|
+
});
|
|
1071
|
+
changed = true;
|
|
1072
|
+
ids = nextIds;
|
|
1073
|
+
cursors = nextCursors;
|
|
1074
|
+
}
|
|
1075
|
+
const dedupe = (values) => {
|
|
1076
|
+
if (!values || !values.includes(previousId)) return;
|
|
1077
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1078
|
+
const next = [];
|
|
1079
|
+
for (const value of values) {
|
|
1080
|
+
const resolved = value === previousId ? nextId : value;
|
|
1081
|
+
if (seen.has(resolved)) continue;
|
|
1082
|
+
seen.add(resolved);
|
|
1083
|
+
next.push(resolved);
|
|
1084
|
+
}
|
|
1085
|
+
changed = true;
|
|
1086
|
+
return next;
|
|
1087
|
+
};
|
|
1088
|
+
const pendingBeforeIds = dedupe(list.pendingBeforeIds) ?? list.pendingBeforeIds;
|
|
1089
|
+
const pendingAfterIds = dedupe(list.pendingAfterIds) ?? list.pendingAfterIds;
|
|
1090
|
+
const liveBeforeIds = dedupe(list.liveBeforeIds) ?? list.liveBeforeIds;
|
|
1091
|
+
const liveAfterIds = dedupe(list.liveAfterIds) ?? list.liveAfterIds;
|
|
1092
|
+
if (!changed) continue;
|
|
1093
|
+
const canonicalIds = new Set(ids);
|
|
1094
|
+
this.setList(key, {
|
|
1095
|
+
backwardPageLimit: list.backwardPageLimit,
|
|
1096
|
+
cursors,
|
|
1097
|
+
forwardPageLimit: list.forwardPageLimit,
|
|
1098
|
+
ids,
|
|
1099
|
+
liveAfterIds: liveAfterIds?.filter((id) => !canonicalIds.has(id)),
|
|
1100
|
+
liveBeforeIds: liveBeforeIds?.filter((id) => !canonicalIds.has(id)),
|
|
1101
|
+
pagination: list.pagination,
|
|
1102
|
+
pendingAfterIds: pendingAfterIds?.filter((id) => !canonicalIds.has(id)),
|
|
1103
|
+
pendingBeforeIds: pendingBeforeIds?.filter((id) => !canonicalIds.has(id))
|
|
1104
|
+
});
|
|
1105
|
+
}
|
|
1106
|
+
});
|
|
1107
|
+
}
|
|
1108
|
+
restoreList(key, list) {
|
|
1109
|
+
return this.update(() => {
|
|
1110
|
+
if (list == null) this.deleteList(key);
|
|
1111
|
+
else this.setList(key, list);
|
|
1112
|
+
});
|
|
1113
|
+
}
|
|
1114
|
+
collectGarbage(markedRecords, markedLists, options = {}) {
|
|
1115
|
+
const records = /* @__PURE__ */ new Set();
|
|
1116
|
+
const lists = /* @__PURE__ */ new Set();
|
|
1117
|
+
for (const id of this.records.keys()) {
|
|
1118
|
+
if (markedRecords.has(id)) continue;
|
|
1119
|
+
records.add(id);
|
|
1120
|
+
}
|
|
1121
|
+
for (const key of this.lists.keys()) {
|
|
1122
|
+
if (markedLists.has(key)) continue;
|
|
1123
|
+
lists.add(key);
|
|
1124
|
+
}
|
|
1125
|
+
for (const id of records) {
|
|
1126
|
+
const record = this.records.get(id);
|
|
1127
|
+
if (record) this.removeRecordReferenceIndexes(id, record);
|
|
1128
|
+
this.records.delete(id);
|
|
1129
|
+
this.coverage.delete(id);
|
|
1130
|
+
options.onRecordDeleted?.(id);
|
|
1131
|
+
}
|
|
1132
|
+
for (const key of lists) this.deleteList(key, true);
|
|
1133
|
+
return {
|
|
1134
|
+
lists,
|
|
1135
|
+
records
|
|
1136
|
+
};
|
|
1137
|
+
}
|
|
1138
|
+
deleteList(key, collected = false) {
|
|
1139
|
+
return this.update(() => {
|
|
1140
|
+
this.captureList(key);
|
|
1141
|
+
const previous = this.lists.get(key);
|
|
1142
|
+
if (previous) this.removeListIndexes(key, previous);
|
|
1143
|
+
this.lists.delete(key);
|
|
1144
|
+
this.notifyListSubscribers(key, !collected, previous);
|
|
1145
|
+
});
|
|
1146
|
+
}
|
|
1147
|
+
subscribeList(key, fn) {
|
|
1148
|
+
let set = this.listSubscriptions.get(key);
|
|
1149
|
+
if (!set) {
|
|
1150
|
+
set = /* @__PURE__ */ new Set();
|
|
1151
|
+
this.listSubscriptions.set(key, set);
|
|
1152
|
+
}
|
|
1153
|
+
set.add(fn);
|
|
1154
|
+
return () => {
|
|
1155
|
+
const subscribers = this.listSubscriptions.get(key);
|
|
1156
|
+
if (!subscribers) return;
|
|
1157
|
+
subscribers.delete(fn);
|
|
1158
|
+
if (subscribers.size === 0) this.listSubscriptions.delete(key);
|
|
1159
|
+
};
|
|
1160
|
+
}
|
|
1161
|
+
removeReferencesTo(targetId, viewDataCache) {
|
|
1162
|
+
return this.update(() => {
|
|
1163
|
+
const listKeys = [...this.listKeysByReferencedEntity.get(targetId) ?? []];
|
|
1164
|
+
for (const key of listKeys) {
|
|
1165
|
+
const list = this.lists.get(key);
|
|
1166
|
+
if (!list) continue;
|
|
1167
|
+
const { ids } = list;
|
|
1168
|
+
const hasLiveAfter = Boolean(list.liveAfterIds?.includes(targetId));
|
|
1169
|
+
const hasLiveBefore = Boolean(list.liveBeforeIds?.includes(targetId));
|
|
1170
|
+
const hasPendingAfter = Boolean(list.pendingAfterIds?.includes(targetId));
|
|
1171
|
+
const hasPendingBefore = Boolean(list.pendingBeforeIds?.includes(targetId));
|
|
1172
|
+
if (!ids.includes(targetId) && !hasLiveAfter && !hasLiveBefore && !hasPendingAfter && !hasPendingBefore) continue;
|
|
1173
|
+
const entityIds = [];
|
|
1174
|
+
const cursors = list.cursors ? [] : void 0;
|
|
1175
|
+
for (let index = 0; index < ids.length; index++) {
|
|
1176
|
+
const id = ids[index];
|
|
1177
|
+
if (id === targetId) continue;
|
|
1178
|
+
entityIds.push(id);
|
|
1179
|
+
if (cursors) cursors.push(list.cursors?.[index]);
|
|
1180
|
+
}
|
|
1181
|
+
this.setList(key, {
|
|
1182
|
+
backwardPageLimit: list.backwardPageLimit,
|
|
1183
|
+
cursors,
|
|
1184
|
+
forwardPageLimit: list.forwardPageLimit,
|
|
1185
|
+
ids: entityIds,
|
|
1186
|
+
liveAfterIds: list.liveAfterIds?.filter((id) => id !== targetId),
|
|
1187
|
+
liveBeforeIds: list.liveBeforeIds?.filter((id) => id !== targetId),
|
|
1188
|
+
pagination: list.pagination,
|
|
1189
|
+
pendingAfterIds: list.pendingAfterIds?.filter((id) => id !== targetId),
|
|
1190
|
+
pendingBeforeIds: list.pendingBeforeIds?.filter((id) => id !== targetId)
|
|
1191
|
+
});
|
|
1192
|
+
}
|
|
1193
|
+
const ids = /* @__PURE__ */ new Map();
|
|
1194
|
+
const recordEntries = [...this.recordReferencesByTarget.get(targetId)?.entries() ?? []];
|
|
1195
|
+
for (const [id, fields] of recordEntries) {
|
|
1196
|
+
const record = this.records.get(id);
|
|
1197
|
+
if (!record) continue;
|
|
1198
|
+
let updated = false;
|
|
1199
|
+
const next = {};
|
|
1200
|
+
const paths = /* @__PURE__ */ new Set();
|
|
1201
|
+
for (const key of fields) {
|
|
1202
|
+
const value = record[key];
|
|
1203
|
+
if (Array.isArray(value)) {
|
|
1204
|
+
const filtered = value.filter((item) => !(isNodeRef(item) && getNodeRefId(item) === targetId));
|
|
1205
|
+
if (filtered.length !== value.length) {
|
|
1206
|
+
updated = true;
|
|
1207
|
+
paths.add(key);
|
|
1208
|
+
next[key] = filtered;
|
|
1209
|
+
}
|
|
1210
|
+
} else if (isNodeRef(value) && getNodeRefId(value) === targetId) {
|
|
1211
|
+
updated = true;
|
|
1212
|
+
paths.add(key);
|
|
1213
|
+
next[key] = null;
|
|
1214
|
+
}
|
|
1215
|
+
}
|
|
1216
|
+
if (!updated) continue;
|
|
1217
|
+
viewDataCache.invalidate(id);
|
|
1218
|
+
this.mergeInternal(id, next, paths);
|
|
1219
|
+
ids.set(id, paths);
|
|
1220
|
+
}
|
|
1221
|
+
for (const [id, paths] of ids) this.notify(id, paths);
|
|
1222
|
+
});
|
|
1223
|
+
}
|
|
1224
|
+
restore(id, snapshot) {
|
|
1225
|
+
return this.update(() => {
|
|
1226
|
+
this.captureRecord(id);
|
|
1227
|
+
const previous = this.records.get(id);
|
|
1228
|
+
if (previous) this.removeRecordReferenceIndexes(id, previous);
|
|
1229
|
+
if (snapshot.record === void 0) this.records.delete(id);
|
|
1230
|
+
else {
|
|
1231
|
+
this.records.set(id, snapshot.record);
|
|
1232
|
+
this.addRecordReferenceIndexes(id, snapshot.record);
|
|
1233
|
+
}
|
|
1234
|
+
if (snapshot.mask === void 0) this.coverage.delete(id);
|
|
1235
|
+
else this.coverage.set(id, snapshot.mask);
|
|
1236
|
+
this.notify(id);
|
|
1237
|
+
});
|
|
1238
|
+
}
|
|
1239
|
+
addListIndexes(key, list) {
|
|
1240
|
+
const parsed = parseListKey(key);
|
|
1241
|
+
if (parsed) {
|
|
1242
|
+
const ownerFieldKey = getOwnerFieldKey(parsed.ownerId, parsed.field);
|
|
1243
|
+
let keys = this.listKeysByOwnerField.get(ownerFieldKey);
|
|
1244
|
+
if (!keys) {
|
|
1245
|
+
keys = /* @__PURE__ */ new Set();
|
|
1246
|
+
this.listKeysByOwnerField.set(ownerFieldKey, keys);
|
|
1247
|
+
}
|
|
1248
|
+
keys.add(key);
|
|
1249
|
+
}
|
|
1250
|
+
for (const id of this.getListReferencedEntityIds(list)) {
|
|
1251
|
+
let keys = this.listKeysByReferencedEntity.get(id);
|
|
1252
|
+
if (!keys) {
|
|
1253
|
+
keys = /* @__PURE__ */ new Set();
|
|
1254
|
+
this.listKeysByReferencedEntity.set(id, keys);
|
|
1255
|
+
}
|
|
1256
|
+
keys.add(key);
|
|
1257
|
+
}
|
|
1258
|
+
}
|
|
1259
|
+
removeListIndexes(key, list) {
|
|
1260
|
+
const parsed = parseListKey(key);
|
|
1261
|
+
if (parsed) {
|
|
1262
|
+
const ownerFieldKey = getOwnerFieldKey(parsed.ownerId, parsed.field);
|
|
1263
|
+
const keys = this.listKeysByOwnerField.get(ownerFieldKey);
|
|
1264
|
+
if (keys) {
|
|
1265
|
+
keys.delete(key);
|
|
1266
|
+
if (keys.size === 0) this.listKeysByOwnerField.delete(ownerFieldKey);
|
|
1267
|
+
}
|
|
1268
|
+
}
|
|
1269
|
+
for (const id of this.getListReferencedEntityIds(list)) {
|
|
1270
|
+
const keys = this.listKeysByReferencedEntity.get(id);
|
|
1271
|
+
if (!keys) continue;
|
|
1272
|
+
keys.delete(key);
|
|
1273
|
+
if (keys.size === 0) this.listKeysByReferencedEntity.delete(id);
|
|
1274
|
+
}
|
|
1275
|
+
}
|
|
1276
|
+
getListReferencedEntityIds(list) {
|
|
1277
|
+
const ids = /* @__PURE__ */ new Set();
|
|
1278
|
+
for (const id of list.ids) ids.add(id);
|
|
1279
|
+
for (const id of list.liveAfterIds ?? []) ids.add(id);
|
|
1280
|
+
for (const id of list.liveBeforeIds ?? []) ids.add(id);
|
|
1281
|
+
for (const id of list.pendingAfterIds ?? []) ids.add(id);
|
|
1282
|
+
for (const id of list.pendingBeforeIds ?? []) ids.add(id);
|
|
1283
|
+
return ids;
|
|
1284
|
+
}
|
|
1285
|
+
getRecordFieldReferences(value) {
|
|
1286
|
+
if (Array.isArray(value)) {
|
|
1287
|
+
let ids = null;
|
|
1288
|
+
for (const item of value) {
|
|
1289
|
+
if (!isNodeRef(item)) continue;
|
|
1290
|
+
if (!ids) ids = /* @__PURE__ */ new Set();
|
|
1291
|
+
ids.add(getNodeRefId(item));
|
|
1292
|
+
}
|
|
1293
|
+
return ids;
|
|
1294
|
+
}
|
|
1295
|
+
if (isNodeRef(value)) return /* @__PURE__ */ new Set([getNodeRefId(value)]);
|
|
1296
|
+
return null;
|
|
1297
|
+
}
|
|
1298
|
+
addRecordFieldReferenceIndex(id, field, value) {
|
|
1299
|
+
const targets = this.getRecordFieldReferences(value);
|
|
1300
|
+
if (!targets || targets.size === 0) return;
|
|
1301
|
+
let fields = this.recordReferenceFields.get(id);
|
|
1302
|
+
if (!fields) {
|
|
1303
|
+
fields = /* @__PURE__ */ new Map();
|
|
1304
|
+
this.recordReferenceFields.set(id, fields);
|
|
1305
|
+
}
|
|
1306
|
+
fields.set(field, targets);
|
|
1307
|
+
for (const targetId of targets) {
|
|
1308
|
+
let records = this.recordReferencesByTarget.get(targetId);
|
|
1309
|
+
if (!records) {
|
|
1310
|
+
records = /* @__PURE__ */ new Map();
|
|
1311
|
+
this.recordReferencesByTarget.set(targetId, records);
|
|
1312
|
+
}
|
|
1313
|
+
let targetFields = records.get(id);
|
|
1314
|
+
if (!targetFields) {
|
|
1315
|
+
targetFields = /* @__PURE__ */ new Set();
|
|
1316
|
+
records.set(id, targetFields);
|
|
1317
|
+
}
|
|
1318
|
+
targetFields.add(field);
|
|
1319
|
+
}
|
|
1320
|
+
}
|
|
1321
|
+
removeRecordFieldReferenceIndex(id, field) {
|
|
1322
|
+
const fields = this.recordReferenceFields.get(id);
|
|
1323
|
+
const targets = fields?.get(field);
|
|
1324
|
+
if (!fields || !targets) return;
|
|
1325
|
+
for (const targetId of targets) {
|
|
1326
|
+
const records = this.recordReferencesByTarget.get(targetId);
|
|
1327
|
+
const targetFields = records?.get(id);
|
|
1328
|
+
if (!records || !targetFields) continue;
|
|
1329
|
+
targetFields.delete(field);
|
|
1330
|
+
if (targetFields.size === 0) records.delete(id);
|
|
1331
|
+
if (records.size === 0) this.recordReferencesByTarget.delete(targetId);
|
|
1332
|
+
}
|
|
1333
|
+
fields.delete(field);
|
|
1334
|
+
if (fields.size === 0) this.recordReferenceFields.delete(id);
|
|
1335
|
+
}
|
|
1336
|
+
addRecordReferenceIndexes(id, record) {
|
|
1337
|
+
for (const [field, value] of Object.entries(record)) this.addRecordFieldReferenceIndex(id, field, value);
|
|
1338
|
+
}
|
|
1339
|
+
removeRecordReferenceIndexes(id, record) {
|
|
1340
|
+
for (const field of Object.keys(record)) this.removeRecordFieldReferenceIndex(id, field);
|
|
1341
|
+
}
|
|
1342
|
+
updateRecordReferenceIndexes(id, previous, next, fields) {
|
|
1343
|
+
for (const field of fields) {
|
|
1344
|
+
if (previous) this.removeRecordFieldReferenceIndex(id, field);
|
|
1345
|
+
this.addRecordFieldReferenceIndex(id, field, next[field]);
|
|
1346
|
+
}
|
|
1347
|
+
}
|
|
1348
|
+
};
|
|
1349
|
+
//#endregion
|
|
1350
|
+
export { isDeferredSelection as C, isDeferred as S, view as _, mutation as a, getDeferredMetadata as b, getDeferredSelectionPlan as c, createRefWithViewNames as d, getRootViewNames as f, getViewPayloads as g, getViewNames as h, getErrorStatusCode as i, getSelectionPlan as l, toEntityId as m, getListKey as n, prepareMutation as o, parseEntityId as p, mergePreservingExisting as r, wrapMutation as s, Store as t, assignViewTag as u, createDeferred as v, getDeferredSelection as x, defer as y };
|