foldkit-remote 0.21.0 → 0.22.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 +977 -193
- package/dist/answerGaps.d.ts +16 -0
- package/dist/answerGaps.d.ts.map +1 -0
- package/dist/answerGaps.js +60 -0
- package/dist/answerGaps.js.map +1 -0
- package/dist/client-B055kQuP.d.ts +110 -0
- package/dist/client-B055kQuP.d.ts.map +1 -0
- package/dist/client.d.ts +2 -0
- package/dist/client.js +142 -0
- package/dist/client.js.map +1 -0
- package/dist/coalesce-CLAr92Nk.d.ts +34 -0
- package/dist/coalesce-CLAr92Nk.d.ts.map +1 -0
- package/dist/coalesce.d.ts +2 -0
- package/dist/coalesce.js +131 -0
- package/dist/coalesce.js.map +1 -0
- package/dist/connection-BRyaJy89.d.ts +45 -0
- package/dist/connection-BRyaJy89.d.ts.map +1 -0
- package/dist/connection.d.ts +2 -0
- package/dist/connection.js +170 -0
- package/dist/connection.js.map +1 -0
- package/dist/data.d.ts +7 -0
- package/dist/data.d.ts.map +1 -0
- package/dist/data.js +21 -0
- package/dist/data.js.map +1 -0
- package/dist/diagnostics-DHhVYflV.d.ts +125 -0
- package/dist/diagnostics-DHhVYflV.d.ts.map +1 -0
- package/dist/diagnostics.d.ts +2 -0
- package/dist/diagnostics.js +60 -0
- package/dist/diagnostics.js.map +1 -0
- package/dist/entity-CgsG5Uel.d.ts +196 -0
- package/dist/entity-CgsG5Uel.d.ts.map +1 -0
- package/dist/entity-DuDxaa3n.js +279 -0
- package/dist/entity-DuDxaa3n.js.map +1 -0
- package/dist/entity.d.ts +2 -0
- package/dist/entity.js +1 -0
- package/dist/held.d.ts +19 -0
- package/dist/held.d.ts.map +1 -0
- package/dist/held.js +70 -0
- package/dist/held.js.map +1 -0
- package/dist/impact.d.ts +79 -0
- package/dist/impact.d.ts.map +1 -0
- package/dist/impact.js +166 -0
- package/dist/impact.js.map +1 -0
- package/dist/index.d.ts +1542 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +2791 -0
- package/dist/index.js.map +1 -0
- package/dist/inspect-DtRQGCRP.d.ts +134 -0
- package/dist/inspect-DtRQGCRP.d.ts.map +1 -0
- package/dist/inspect.d.ts +2 -0
- package/dist/inspect.js +33 -0
- package/dist/inspect.js.map +1 -0
- package/dist/json-DowLl1eB.d.ts +52 -0
- package/dist/json-DowLl1eB.d.ts.map +1 -0
- package/dist/json.d.ts +2 -0
- package/dist/json.js +123 -0
- package/dist/json.js.map +1 -0
- package/dist/law.d.ts +56 -0
- package/dist/law.d.ts.map +1 -0
- package/dist/law.js +201 -0
- package/dist/law.js.map +1 -0
- package/dist/live-BpTzlCnY.d.ts +79 -0
- package/dist/live-BpTzlCnY.d.ts.map +1 -0
- package/dist/live.d.ts +2 -0
- package/dist/live.js +126 -0
- package/dist/live.js.map +1 -0
- package/dist/matching-DF5F9Us8.d.ts +100 -0
- package/dist/matching-DF5F9Us8.d.ts.map +1 -0
- package/dist/matching.d.ts +2 -0
- package/dist/matching.js +172 -0
- package/dist/matching.js.map +1 -0
- package/dist/model-B6zk1NMB.d.ts +710 -0
- package/dist/model-B6zk1NMB.d.ts.map +1 -0
- package/dist/model.d.ts +2 -0
- package/dist/model.js +1308 -0
- package/dist/model.js.map +1 -0
- package/dist/mutation.d.ts +2 -0
- package/dist/mutation.js +612 -0
- package/dist/mutation.js.map +1 -0
- package/dist/optimistic-CECsfNGY.d.ts +523 -0
- package/dist/optimistic-CECsfNGY.d.ts.map +1 -0
- package/dist/optimistic.d.ts +2 -0
- package/dist/optimistic.js +336 -0
- package/dist/optimistic.js.map +1 -0
- package/dist/outbox-DNuc4kZp.d.ts +259 -0
- package/dist/outbox-DNuc4kZp.d.ts.map +1 -0
- package/dist/outbox.d.ts +2 -0
- package/dist/outbox.js +326 -0
- package/dist/outbox.js.map +1 -0
- package/dist/patchValues.d.ts +10 -0
- package/dist/patchValues.d.ts.map +1 -0
- package/dist/patchValues.js +57 -0
- package/dist/patchValues.js.map +1 -0
- package/dist/persistence-CCMnvg7-.d.ts +114 -0
- package/dist/persistence-CCMnvg7-.d.ts.map +1 -0
- package/dist/persistence.d.ts +2 -0
- package/dist/persistence.js +205 -0
- package/dist/persistence.js.map +1 -0
- package/dist/placement-jZdsIpmj.d.ts +50 -0
- package/dist/placement-jZdsIpmj.d.ts.map +1 -0
- package/dist/placement.d.ts +2 -0
- package/dist/placement.js +116 -0
- package/dist/placement.js.map +1 -0
- package/dist/plan-D9cftxC_.d.ts +35 -0
- package/dist/plan-D9cftxC_.d.ts.map +1 -0
- package/dist/plan.d.ts +2 -0
- package/dist/plan.js +100 -0
- package/dist/plan.js.map +1 -0
- package/dist/policy-DsWHmIAq.d.ts +42 -0
- package/dist/policy-DsWHmIAq.d.ts.map +1 -0
- package/dist/policy.d.ts +2 -0
- package/dist/policy.js +32 -0
- package/dist/policy.js.map +1 -0
- package/dist/{port.d.mts → port.d.ts} +3 -2
- package/dist/port.d.ts.map +1 -0
- package/dist/{port.mjs → port.js} +3 -2
- package/dist/port.js.map +1 -0
- package/dist/query-BuIU93xA.d.ts +141 -0
- package/dist/query-BuIU93xA.d.ts.map +1 -0
- package/dist/query.d.ts +2 -0
- package/dist/query.js +0 -0
- package/dist/query.js.map +1 -0
- package/dist/record.d.ts +11 -0
- package/dist/record.d.ts.map +1 -0
- package/dist/record.js +21 -0
- package/dist/record.js.map +1 -0
- package/dist/relation-BLBGOZom.d.ts +59 -0
- package/dist/relation-BLBGOZom.d.ts.map +1 -0
- package/dist/relation.d.ts +2 -0
- package/dist/relation.js +93 -0
- package/dist/relation.js.map +1 -0
- package/dist/remoteData-mK1suYi1.d.ts +148 -0
- package/dist/remoteData-mK1suYi1.d.ts.map +1 -0
- package/dist/remoteData.d.ts +2 -0
- package/dist/remoteData.js +179 -0
- package/dist/remoteData.js.map +1 -0
- package/dist/reporting.d.ts +12 -0
- package/dist/reporting.d.ts.map +1 -0
- package/dist/reporting.js +45 -0
- package/dist/reporting.js.map +1 -0
- package/dist/requirement-D57S2LpH.d.ts +64 -0
- package/dist/requirement-D57S2LpH.d.ts.map +1 -0
- package/dist/requirement.d.ts +2 -0
- package/dist/requirement.js +114 -0
- package/dist/requirement.js.map +1 -0
- package/dist/resume-Ct5R6yli.d.ts +92 -0
- package/dist/resume-Ct5R6yli.d.ts.map +1 -0
- package/dist/resume.d.ts +2 -0
- package/dist/resume.js +222 -0
- package/dist/resume.js.map +1 -0
- package/dist/retain-BfNTvBRb.d.ts +48 -0
- package/dist/retain-BfNTvBRb.d.ts.map +1 -0
- package/dist/retain.d.ts +2 -0
- package/dist/retain.js +107 -0
- package/dist/retain.js.map +1 -0
- package/dist/selection.d.ts +2 -0
- package/dist/selection.js +1 -0
- package/dist/shown.d.ts +20 -0
- package/dist/shown.d.ts.map +1 -0
- package/dist/shown.js +40 -0
- package/dist/shown.js.map +1 -0
- package/dist/sse-C7LhdbiQ.d.ts +17 -0
- package/dist/sse-C7LhdbiQ.d.ts.map +1 -0
- package/dist/sse.d.ts +2 -0
- package/dist/sse.js +127 -0
- package/dist/sse.js.map +1 -0
- package/dist/store-ah2qjcJW.d.ts +100 -0
- package/dist/store-ah2qjcJW.d.ts.map +1 -0
- package/dist/store.d.ts +2 -0
- package/dist/store.js +218 -0
- package/dist/store.js.map +1 -0
- package/dist/testing.d.ts +133 -0
- package/dist/testing.d.ts.map +1 -0
- package/dist/testing.js +0 -0
- package/dist/testing.js.map +1 -0
- package/dist/why-kgp_Wr3z.d.ts +483 -0
- package/dist/why-kgp_Wr3z.d.ts.map +1 -0
- package/dist/why.d.ts +2 -0
- package/dist/why.js +293 -0
- package/dist/why.js.map +1 -0
- package/dist/whyReasons.d.ts +42 -0
- package/dist/whyReasons.d.ts.map +1 -0
- package/dist/whyReasons.js +211 -0
- package/dist/whyReasons.js.map +1 -0
- package/dist/wire-DthPcTM6.d.ts +787 -0
- package/dist/wire-DthPcTM6.d.ts.map +1 -0
- package/dist/wire.d.ts +2 -0
- package/dist/wire.js +329 -0
- package/dist/wire.js.map +1 -0
- package/package.json +18 -13
- package/dist/index-YyucgTYm.d.mts +0 -4115
- package/dist/index-YyucgTYm.d.mts.map +0 -1
- package/dist/index.d.mts +0 -2
- package/dist/index.mjs +0 -1
- package/dist/port.d.mts.map +0 -1
- package/dist/port.mjs.map +0 -1
- package/dist/src-lVP_7bf4.mjs +0 -6382
- package/dist/src-lVP_7bf4.mjs.map +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"policy-DsWHmIAq.d.ts","names":[],"sources":["../src/policy.ts"],"mappings":";KAQY;;;WAEG;;;;;;;WAKA;WAAuC;;;;WAEvC;;cAEF;;;aAES;;;EAGY,uBAAA;aAAW;QAAmB;;;aAMzC;;;EAGJ,SAAA,QAAA,cAAY,gBAAgB;;;;;;EAgBzB,YAAA,QAAA"}
|
package/dist/policy.d.ts
ADDED
package/dist/policy.js
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
// src/policy.ts
|
|
2
|
+
const RemotePolicy = {
|
|
3
|
+
/** Fetch only what the store lacks. */
|
|
4
|
+
cacheFirst: { _tag: "CacheFirst" },
|
|
5
|
+
/** Keep present values visible, read as `Refreshing`, and refetch an entry older than `maxAge` ms. */
|
|
6
|
+
staleWhileRevalidate: (options) => ({
|
|
7
|
+
_tag: "StaleWhileRevalidate",
|
|
8
|
+
maxAge: options.maxAge
|
|
9
|
+
}),
|
|
10
|
+
/** Fetch every selected field regardless of coverage; cached values stay visible meanwhile. */
|
|
11
|
+
networkOnly: { _tag: "NetworkOnly" },
|
|
12
|
+
/** The planner options a policy compiles to at `now`. */
|
|
13
|
+
toPlan: (policy, now) => {
|
|
14
|
+
switch (policy._tag) {
|
|
15
|
+
case "CacheFirst": return {};
|
|
16
|
+
case "StaleWhileRevalidate": return { freshness: {
|
|
17
|
+
now,
|
|
18
|
+
freshness: policy.maxAge
|
|
19
|
+
} };
|
|
20
|
+
case "NetworkOnly": return { force: true };
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
/**
|
|
24
|
+
* Whether a read under this policy may refetch fields the store already
|
|
25
|
+
* holds, so an observer marks them stale (read as `Refreshing`) while the
|
|
26
|
+
* request is pending.
|
|
27
|
+
*/
|
|
28
|
+
refreshes: (policy) => policy._tag !== "CacheFirst"
|
|
29
|
+
};
|
|
30
|
+
export { RemotePolicy };
|
|
31
|
+
|
|
32
|
+
//# sourceMappingURL=policy.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"policy.js","names":[],"sources":["../src/policy.ts"],"sourcesContent":["/**\n * Request policies: how a read treats fields the store already holds. A policy\n * compiles to planner options; it is not a second cache. `Remote.observe` and\n * `Remote.prefetch` take one, and the planner itself stays pure and\n * time-injected.\n */\nimport type { PlanOptions } from './plan.js'\n\nexport type RemotePolicy =\n /** Fetch only fields the store lacks (missing, stale, or re-windowed). */\n | { readonly _tag: 'CacheFirst' }\n /**\n * Keep present values visible, read them as `Refreshing`, and refetch an\n * entry older than `maxAge` milliseconds.\n */\n | { readonly _tag: 'StaleWhileRevalidate'; readonly maxAge: number }\n /** Fetch every selected field regardless of coverage; cached values stay visible meanwhile. */\n | { readonly _tag: 'NetworkOnly' }\n\nexport const RemotePolicy = {\n /** Fetch only what the store lacks. */\n cacheFirst: { _tag: 'CacheFirst' } as const satisfies RemotePolicy,\n\n /** Keep present values visible, read as `Refreshing`, and refetch an entry older than `maxAge` ms. */\n staleWhileRevalidate: (options: { readonly maxAge: number }): RemotePolicy => ({\n _tag: 'StaleWhileRevalidate',\n maxAge: options.maxAge,\n }),\n\n /** Fetch every selected field regardless of coverage; cached values stay visible meanwhile. */\n networkOnly: { _tag: 'NetworkOnly' } as const satisfies RemotePolicy,\n\n /** The planner options a policy compiles to at `now`. */\n toPlan: (policy: RemotePolicy, now: number): PlanOptions => {\n switch (policy._tag) {\n case 'CacheFirst':\n return {}\n case 'StaleWhileRevalidate':\n return { freshness: { now, freshness: policy.maxAge } }\n case 'NetworkOnly':\n return { force: true }\n }\n },\n\n /**\n * Whether a read under this policy may refetch fields the store already\n * holds, so an observer marks them stale (read as `Refreshing`) while the\n * request is pending.\n */\n refreshes: (policy: RemotePolicy): boolean => policy._tag !== 'CacheFirst',\n}\n"],"mappings":";AAmBA,MAAa,eAAe;;CAE1B,YAAY,EAAE,MAAM,aAAa;;CAGjC,uBAAuB,aAAwD;EAC7E,MAAM;EACN,QAAQ,QAAQ;CAClB;;CAGA,aAAa,EAAE,MAAM,cAAc;;CAGnC,SAAS,QAAsB,QAA6B;EAC1D,QAAQ,OAAO,MAAf;GACE,KAAK,cACH,OAAO,CAAC;GACV,KAAK,wBACH,OAAO,EAAE,WAAW;IAAE;IAAK,WAAW,OAAO;GAAO,EAAE;GACxD,KAAK,eACH,OAAO,EAAE,OAAO,KAAK;EACzB;CACF;;;;;;CAOA,YAAY,WAAkC,OAAO,SAAS;AAChE"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { t as RemoteClient } from "./client-B055kQuP.js";
|
|
2
|
+
import "./index.js";
|
|
2
3
|
import { Effect, Layer } from "effect";
|
|
3
4
|
import { RpcClient } from "effect/rpc";
|
|
4
5
|
import * as WorkerRunner from "effect/workers/WorkerRunner";
|
|
@@ -23,4 +24,4 @@ export declare const portRunner: (served: MessagePort) => {
|
|
|
23
24
|
readonly platform: WorkerRunner.WorkerRunnerPlatform['Service'];
|
|
24
25
|
readonly closed: Effect.Effect<void>;
|
|
25
26
|
};
|
|
26
|
-
//# sourceMappingURL=port.d.
|
|
27
|
+
//# sourceMappingURL=port.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"port.d.ts","names":[],"sources":["../src/port.ts"],"mappings":";;;;;;;;;;;;;qBA8Ca,eAAY,YACX,gBACX,MAAM,MAAM,UAAU,UAAU;;qBAWtB,OAAI,YAAgB,gBAAc,MAAM,MAAM;;;;;;;qBAc9C,aAAU,QACb;WAEC,UAAU,aAAa;WACvB,QAAQ,OAAO"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { RemoteRpc } from "./wire.js";
|
|
2
|
+
import { Remote } from "./index.js";
|
|
2
3
|
import { Deferred, Effect, Exit, Layer, Queue, Scope } from "effect";
|
|
3
4
|
import { RpcClient } from "effect/rpc";
|
|
4
5
|
import * as Worker from "effect/workers/Worker";
|
|
@@ -77,4 +78,4 @@ const portRunner = (served) => {
|
|
|
77
78
|
};
|
|
78
79
|
export { port, portProtocol, portRunner };
|
|
79
80
|
|
|
80
|
-
//# sourceMappingURL=port.
|
|
81
|
+
//# sourceMappingURL=port.js.map
|
package/dist/port.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"port.js","names":[],"sources":["../src/port.ts"],"sourcesContent":["/**\n * `foldkit-remote/port`: Remote over a `MessagePort`, for a server in a\n * worker or in the page (a sandbox). It speaks Effect's own RPC worker\n * protocol over the port, so reads, queries, mutations and live streams, their\n * typed errors and interruption all cross it; `foldkit-remote-server/port`\n * serves the other end.\n *\n * Built on `effect/workers` alone: a `MessagePort` platform for the client and\n * a one-port runner for the server, no browser package.\n */\nimport { Deferred, Effect, Exit, Layer, Queue, Scope } from 'effect'\nimport { RpcClient } from 'effect/rpc'\nimport * as Worker from 'effect/workers/Worker'\nimport type { WorkerError } from 'effect/workers/WorkerError'\nimport * as WorkerRunner from 'effect/workers/WorkerRunner'\nimport { Remote, type RemoteClient } from './index.js'\nimport { RemoteRpc } from './wire.js'\n\nconst platform = Worker.makePlatform<MessagePort>()({\n // Closing the client's scope tells the server the conversation is over.\n setup: ({ worker, scope }) =>\n Effect.as(\n Scope.addFinalizer(\n scope,\n Effect.sync(() => worker.postMessage([1])),\n ),\n worker,\n ),\n listen: ({ port, emit, scope }) => {\n const onMessage = (event: MessageEvent) => emit(event.data)\n port.addEventListener('message', onMessage)\n port.start()\n return Scope.addFinalizer(\n scope,\n Effect.sync(() => port.removeEventListener('message', onMessage)),\n )\n },\n})\n\n/**\n * Effect's RPC worker protocol over a `MessagePort`: what `port` runs on, for\n * an RPC client of `RemoteRpc` made by hand. `open` is called for each\n * connection the protocol makes (once, and again if it restarts), so it opens\n * a new conversation each time, such as a `MessageChannel` whose other port\n * goes to the server.\n */\nexport const portProtocol = (\n open: () => MessagePort,\n): Layer.Layer<RpcClient.Protocol, WorkerError> =>\n RpcClient.layerProtocolWorker({ size: 1 }).pipe(\n Layer.provide(\n Layer.merge(\n Layer.succeed(Worker.WorkerPlatform)(platform),\n Worker.layerSpawner(() => open()),\n ),\n ),\n )\n\n/** Remote's client over a `MessagePort`: `portProtocol` under `Remote.clientLayer`. */\nexport const port = (open: () => MessagePort): Layer.Layer<RemoteClient> =>\n Layer.unwrap(Effect.map(RpcClient.make(RemoteRpc), client => Remote.clientLayer(client))).pipe(\n Layer.provide(portProtocol(open)),\n // A port that cannot be set up is a broken sandbox, not a failed read:\n // each call's own failure is a Remote error, as over HTTP.\n Layer.orDie,\n )\n\n/**\n * The server's side of one port: Effect's `WorkerRunnerPlatform` over a single\n * `MessagePort`, for `RpcServer.layerProtocolWorkerRunner`, and `closed`,\n * which completes when the client closes its end. `foldkit-remote-server/port`\n * uses it.\n */\nexport const portRunner = (\n served: MessagePort,\n): {\n readonly platform: WorkerRunner.WorkerRunnerPlatform['Service']\n readonly closed: Effect.Effect<void>\n} => {\n const closed = Deferred.makeUnsafe<void>()\n const platform: WorkerRunner.WorkerRunnerPlatform['Service'] = {\n start: <O, I>() =>\n Effect.gen(function* () {\n const disconnects = yield* Queue.make<number>()\n const sendUnsafe = (_: number, message: O, transfer?: ReadonlyArray<unknown>) =>\n served.postMessage([1, message], {\n transfer: [...(transfer ?? [])] as Array<Transferable>,\n })\n const run = <A, E, R>(\n handler: (portId: number, message: I) => Effect.Effect<A, E, R> | void,\n ) =>\n Effect.scopedWith(scope =>\n Effect.gen(function* () {\n const fork = Effect.runForkWith(yield* Effect.context<R>())\n const onMessage = (event: MessageEvent) => {\n const message = event.data as WorkerRunner.PlatformMessage<I>\n if (message[0] === 1) {\n Deferred.doneUnsafe(closed, Exit.void)\n return\n }\n const result = handler(0, message[1])\n if (Effect.isEffect(result)) fork(result)\n }\n served.addEventListener('message', onMessage)\n served.start()\n served.postMessage([0])\n yield* Scope.addFinalizer(\n scope,\n Effect.sync(() => {\n served.removeEventListener('message', onMessage)\n served.close()\n }),\n )\n yield* Deferred.await(closed)\n }),\n )\n const runner: WorkerRunner.WorkerRunner<O, I> = {\n run,\n send: (portId, message, transfer) =>\n Effect.sync(() => sendUnsafe(portId, message, transfer)),\n sendUnsafe,\n disconnects,\n }\n return runner\n }),\n }\n return { platform, closed: Deferred.await(closed) }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;AAkBA,MAAM,WAAW,OAAO,aAA0B,CAAC,CAAC;CAElD,QAAQ,EAAE,QAAQ,YAChB,OAAO,GACL,MAAM,aACJ,OACA,OAAO,WAAW,OAAO,YAAY,CAAC,CAAC,CAAC,CAAC,CAC3C,GACA,MACF;CACF,SAAS,EAAE,MAAM,MAAM,YAAY;EACjC,MAAM,aAAa,UAAwB,KAAK,MAAM,IAAI;EAC1D,KAAK,iBAAiB,WAAW,SAAS;EAC1C,KAAK,MAAM;EACX,OAAO,MAAM,aACX,OACA,OAAO,WAAW,KAAK,oBAAoB,WAAW,SAAS,CAAC,CAClE;CACF;AACF,CAAC;;;;;;;;AASD,MAAa,gBACX,SAEA,UAAU,oBAAoB,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC,KACzC,MAAM,QACJ,MAAM,MACJ,MAAM,QAAQ,OAAO,cAAc,CAAC,CAAC,QAAQ,GAC7C,OAAO,mBAAmB,KAAK,CAAC,CAClC,CACF,CACF;;AAGF,MAAa,QAAQ,SACnB,MAAM,OAAO,OAAO,IAAI,UAAU,KAAK,SAAS,IAAG,WAAU,OAAO,YAAY,MAAM,CAAC,CAAC,CAAC,CAAC,KACxF,MAAM,QAAQ,aAAa,IAAI,CAAC,GAGhC,MAAM,KACR;;;;;;;AAQF,MAAa,cACX,WAIG;CACH,MAAM,SAAS,SAAS,WAAiB;CA+CzC,OAAO;EAAE,UAAA,EA7CP,aACE,OAAO,IAAI,aAAa;GACtB,MAAM,cAAc,OAAO,MAAM,KAAa;GAC9C,MAAM,cAAc,GAAW,SAAY,aACzC,OAAO,YAAY,CAAC,GAAG,OAAO,GAAG,EAC/B,UAAU,CAAC,GAAI,YAAY,CAAC,CAAE,EAChC,CAAC;GACH,MAAM,OACJ,YAEA,OAAO,YAAW,UAChB,OAAO,IAAI,aAAa;IACtB,MAAM,OAAO,OAAO,YAAY,OAAO,OAAO,QAAW,CAAC;IAC1D,MAAM,aAAa,UAAwB;KACzC,MAAM,UAAU,MAAM;KACtB,IAAI,QAAQ,OAAO,GAAG;MACpB,SAAS,WAAW,QAAQ,KAAK,IAAI;MACrC;KACF;KACA,MAAM,SAAS,QAAQ,GAAG,QAAQ,EAAE;KACpC,IAAI,OAAO,SAAS,MAAM,GAAG,KAAK,MAAM;IAC1C;IACA,OAAO,iBAAiB,WAAW,SAAS;IAC5C,OAAO,MAAM;IACb,OAAO,YAAY,CAAC,CAAC,CAAC;IACtB,OAAO,MAAM,aACX,OACA,OAAO,WAAW;KAChB,OAAO,oBAAoB,WAAW,SAAS;KAC/C,OAAO,MAAM;IACf,CAAC,CACH;IACA,OAAO,SAAS,MAAM,MAAM;GAC9B,CAAC,CACH;GAQF,OAAO;IANL;IACA,OAAO,QAAQ,SAAS,aACtB,OAAO,WAAW,WAAW,QAAQ,SAAS,QAAQ,CAAC;IACzD;IACA;GAEU;EACd,CAAC,EAEW;EAAG,QAAQ,SAAS,MAAM,MAAM;CAAE;AACpD"}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { r as Cursor } from "./connection-BRyaJy89.js";
|
|
2
|
+
import { F as SchemaOrFields, L as TypeOf } from "./optimistic-CECsfNGY.js";
|
|
3
|
+
import { Schema } from "effect";
|
|
4
|
+
import { AnyEntity, AnyQuery, InputExpr, Query } from "foldkit-entity";
|
|
5
|
+
type LiveInsertion = 'visible' | 'boundary' | 'invalidate' | 'ignore';
|
|
6
|
+
interface LivePolicy {
|
|
7
|
+
readonly prepend?: LiveInsertion;
|
|
8
|
+
readonly append?: LiveInsertion;
|
|
9
|
+
}
|
|
10
|
+
interface ConnectionSpec<Entity extends string = string> {
|
|
11
|
+
readonly entity: Entity;
|
|
12
|
+
readonly edgeKey?: Schema.Schema<unknown>;
|
|
13
|
+
readonly live?: LivePolicy;
|
|
14
|
+
}
|
|
15
|
+
interface QueryWindow {
|
|
16
|
+
readonly first?: number | undefined;
|
|
17
|
+
readonly last?: number | undefined;
|
|
18
|
+
readonly after?: Cursor | undefined;
|
|
19
|
+
readonly before?: Cursor | undefined;
|
|
20
|
+
}
|
|
21
|
+
interface QueryRef<Name extends string, Input> {
|
|
22
|
+
readonly query: Name;
|
|
23
|
+
readonly input: Input;
|
|
24
|
+
/** The input codec, so a `QueryRef` can encode itself without its descriptor. */
|
|
25
|
+
readonly Input: Schema.Codec<Input>;
|
|
26
|
+
readonly window: QueryWindow;
|
|
27
|
+
/** Connection identity: descriptor + canonical input, excluding the window. */
|
|
28
|
+
readonly identity: string;
|
|
29
|
+
}
|
|
30
|
+
interface QueryDescriptor<Name extends string, Input, Result> {
|
|
31
|
+
readonly name: Name;
|
|
32
|
+
readonly Input: Schema.Codec<Input>;
|
|
33
|
+
readonly Result: Result;
|
|
34
|
+
readonly ref: (input: Input) => QueryRef<Name, Input>;
|
|
35
|
+
/**
|
|
36
|
+
* What the query means, when it was declared with `Query.define`: the rows it
|
|
37
|
+
* is about, as a value an interpreter can compile. A descriptor from
|
|
38
|
+
* `Query.make` has none — its meaning lives in whatever the server registered
|
|
39
|
+
* to answer it — and a source is free to answer either.
|
|
40
|
+
*/
|
|
41
|
+
readonly body?: AnyQuery | undefined;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* The inputs of a definition's body: one placeholder per key of `Input`, typed
|
|
45
|
+
* as that key's value. A body is built once, so these stand for what the query
|
|
46
|
+
* will be given rather than being it.
|
|
47
|
+
*/
|
|
48
|
+
type QueryInputs<Input> = { readonly [K in keyof Input & string]: InputExpr<Input[K]>; };
|
|
49
|
+
/**
|
|
50
|
+
* What separates a query's name from its canonical encoded input in a
|
|
51
|
+
* connection identity.
|
|
52
|
+
*
|
|
53
|
+
* NUL, because `JSON.stringify` escapes it inside a string (`\u0000`) and a
|
|
54
|
+
* query name is a TypeScript identifier — so the first occurrence is always the
|
|
55
|
+
* boundary, and a name or an input value cannot forge one. Anything recovering
|
|
56
|
+
* the query from an identity splits here.
|
|
57
|
+
*/
|
|
58
|
+
declare const IDENTITY_SEPARATOR = "\0";
|
|
59
|
+
/** Stable stringify: object keys sorted, undefined-valued keys dropped, so equal inputs encode equally. */
|
|
60
|
+
declare const stableStringify: (value: unknown) => string;
|
|
61
|
+
/**
|
|
62
|
+
* A connection's identity: the query's name and its input, encoded and
|
|
63
|
+
* canonical. What the client keys a list by, and what a server that runs a
|
|
64
|
+
* query from its request (a live list) names the list as.
|
|
65
|
+
*/
|
|
66
|
+
declare const queryIdentity: (query: string, encodedInput: unknown) => string;
|
|
67
|
+
/**
|
|
68
|
+
* `Result: Project` (or anything with the entity's `name`, as `Query.connection`
|
|
69
|
+
* takes) means a connection over it; a `ConnectionSpec` names its `entity`
|
|
70
|
+
* instead and is the result as given.
|
|
71
|
+
*/
|
|
72
|
+
type ResultOf<Result> = Result extends {
|
|
73
|
+
readonly name: infer Entity extends string;
|
|
74
|
+
} ? ConnectionSpec<Entity> : Result;
|
|
75
|
+
/** A relational `Query` over one Entity, as `Query.define`'s body returns. */
|
|
76
|
+
type RelationalQueryOf<E extends AnyEntity> = ReturnType<typeof Query.from<E>>;
|
|
77
|
+
declare const Query$1: {
|
|
78
|
+
from: <E extends AnyEntity>(entity: E) => Query<E>;
|
|
79
|
+
where: <const P extends ReadonlyArray<import("foldkit-entity").Predicate>>(...predicates: P) => <E extends AnyEntity>(self: Query<E> & (string extends import("foldkit-entity").ReadsOf<P[number]> ? unknown : [import("foldkit-entity").ReadsOf<P[number]>] extends [E["name"]] ? unknown : import("foldkit-entity").Invalid<`foldkit-entity: a predicate reads ${Exclude<import("foldkit-entity").ReadsOf<P[number]>, E["name"]>}, but the query is from ${E["name"]}`>)) => Query<E>;
|
|
80
|
+
orderBy: <const T extends ReadonlyArray<import("foldkit-entity").Ordering>>(...terms: T) => <E extends AnyEntity>(self: Query<E> & (string extends import("foldkit-entity").ReadsOf<T[number]> ? unknown : [import("foldkit-entity").ReadsOf<T[number]>] extends [E["name"]] ? unknown : import("foldkit-entity").Invalid<`foldkit-entity: an ordering term reads ${Exclude<import("foldkit-entity").ReadsOf<T[number]>, E["name"]>}, but the query is from ${E["name"]}`>)) => Query<E>;
|
|
81
|
+
orderFor: (self: AnyQuery, input: Readonly<Record<string, unknown>>) => ReadonlyArray<import("foldkit-entity").OrderTerm>;
|
|
82
|
+
placement: (self: AnyQuery, input: Readonly<Record<string, unknown>>, options?: {
|
|
83
|
+
readonly operators?: import("foldkit-entity").OperatorTable | undefined;
|
|
84
|
+
}) => import("foldkit-entity").Placement;
|
|
85
|
+
operators: (self: AnyQuery) => ReadonlyArray<string>;
|
|
86
|
+
dependencies: (self: AnyQuery) => import("foldkit-entity").QueryDependencies;
|
|
87
|
+
unsupported: (self: AnyQuery, supported: ReadonlyArray<import("foldkit-entity").Operation>, operators?: import("foldkit-entity").OperatorTable) => ReadonlyArray<string>;
|
|
88
|
+
show: (self: AnyQuery) => string;
|
|
89
|
+
/** Declares the entity and options a query's result is a connection over. */
|
|
90
|
+
connection: <Entity extends string>(entity: {
|
|
91
|
+
readonly name: Entity;
|
|
92
|
+
}, options?: {
|
|
93
|
+
readonly edgeKey?: Schema.Schema<unknown>;
|
|
94
|
+
readonly live?: LivePolicy;
|
|
95
|
+
}) => ConnectionSpec<Entity>;
|
|
96
|
+
/**
|
|
97
|
+
* Declares a query. `Input` is a codec or the fields of the `Schema.Struct` it
|
|
98
|
+
* would be; `Result` is a `Query.connection(...)`, or the entity the result is
|
|
99
|
+
* a connection over.
|
|
100
|
+
*/
|
|
101
|
+
make: <const Name extends string, Input extends SchemaOrFields, Result>(name: Name, config: {
|
|
102
|
+
readonly Input: Input;
|
|
103
|
+
readonly Result: Result;
|
|
104
|
+
}) => QueryDescriptor<Name, TypeOf<Input>, ResultOf<Result>>;
|
|
105
|
+
/**
|
|
106
|
+
* Declares a query by what it means. The body is built once, here, and the
|
|
107
|
+
* `input` it is given holds placeholders rather than values — there is
|
|
108
|
+
* nothing yet to branch on, so a condition that depends on what was passed is
|
|
109
|
+
* a comparison over the placeholder:
|
|
110
|
+
*
|
|
111
|
+
* ```ts
|
|
112
|
+
* const PostsBySlug = Query.define('PostsBySlug', { slug: Schema.String }, ({ input }) =>
|
|
113
|
+
* Query.from(Post).pipe(
|
|
114
|
+
* Query.where(Expr.eq(Post.fields.slug, input.slug)),
|
|
115
|
+
* Query.orderBy(Order.asc(Post.fields.id)),
|
|
116
|
+
* ),
|
|
117
|
+
* )
|
|
118
|
+
* ```
|
|
119
|
+
*
|
|
120
|
+
* What comes back is an ordinary `QueryDescriptor` — the same name, `Input`,
|
|
121
|
+
* `ref` and connection identity `Query.make` gives — carrying its `body`
|
|
122
|
+
* besides. A server can compile that body instead of being told the same
|
|
123
|
+
* thing again in its own dialect, and one that would rather answer the query
|
|
124
|
+
* its own way still can.
|
|
125
|
+
*
|
|
126
|
+
* The result is a connection over the Entity the body reads, so it is not
|
|
127
|
+
* named twice.
|
|
128
|
+
*/
|
|
129
|
+
define: <const Name extends string, Input extends SchemaOrFields, E extends AnyEntity>(name: Name, Input: Input, body: (context: {
|
|
130
|
+
readonly input: QueryInputs<TypeOf<Input>>;
|
|
131
|
+
}) => RelationalQueryOf<E>, options?: {
|
|
132
|
+
readonly edgeKey?: Schema.Schema<unknown>;
|
|
133
|
+
readonly live?: LivePolicy;
|
|
134
|
+
}) => QueryDescriptor<Name, TypeOf<Input>, ConnectionSpec<E['name']>>;
|
|
135
|
+
first: (count: number) => <R extends QueryRef<string, unknown>>(ref: R) => R;
|
|
136
|
+
last: (count: number) => <R extends QueryRef<string, unknown>>(ref: R) => R;
|
|
137
|
+
after: (cursor: Cursor) => <R extends QueryRef<string, unknown>>(ref: R) => R;
|
|
138
|
+
before: (cursor: Cursor) => <R extends QueryRef<string, unknown>>(ref: R) => R;
|
|
139
|
+
};
|
|
140
|
+
export { Query$1 as a, QueryRef as c, queryIdentity as d, stableStringify as f, LivePolicy as i, QueryWindow as l, IDENTITY_SEPARATOR as n, QueryDescriptor as o, LiveInsertion as r, QueryInputs as s, ConnectionSpec as t, ResultOf as u };
|
|
141
|
+
//# sourceMappingURL=query-BuIU93xA.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"query-BuIU93xA.d.ts","names":["RelationalQuery","Query"],"sources":["../src/query.ts"],"mappings":";;;;KAgBY;UAEK;WACN,UAAU;WACV,SAAS;;UAGH,eAAe;WACrB,QAAQ;WACR,UAAU,OAAO;WACjB,OAAO;;UAGD;WACN;WACA;WACA,QAAQ;WACR,SAAS;;UAGH,SAAS,qBAAqB;WACpC,OAAO;WACP,OAAO;;WAEP,OAAO,OAAO,MAAM;WACpB,QAAQ;;WAER;;UAGM,gBAAgB,qBAAqB,OAAO;WAClD,MAAM;WACN,OAAO,OAAO,MAAM;WACpB,QAAQ;WACR,MAAM,OAAO,UAAU,SAAS,MAAM;;;;;;;WAOtC,OAAO;;;;;;;KAQN,YAAY,qBACZ,WAAW,iBAAiB,UAAU,MAAM;;;;;;;;;;cAY3C;;cAGA,kBAAe;;;;;;cAiBf,gBAAa,eAAiB;;;;;;KAQ/B,SAAS,UAAU;WAA0B,YAAY;IACjE,eAAe,UACf;;KA0CC,kBAAkB,UAAU,aAAa,kBAAkBA,MAAgB,KAAK;cAExEC;;;;;;;;;;;;;EAIE,aAAA,uBAAqB;aACb,MAAM;KAAQ;aACZ,UAAU,OAAO;aAA0B,OAAO;QACtE,eAAe;;;;;;EAWX,aAAM,qBAAqB,cAAc,gBAAgB,QAAM,MAC9D,MAAI;aACS,OAAO;aAAgB,QAAQ;QACjD,gBAAgB,MAAM,OAAO,QAAQ,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;EA2CxC,eAAM,qBAAqB,cAAc,gBAAgB,UAAU,WAAS,MAC7E,MAAI,OACH,OAAK,OACL;aAAoB,OAAO,YAAY,OAAO;QAAc,kBAAkB,IAAE;aAClE,UAAU,OAAO;aAA0B,OAAO;QACtE,gBAAgB,MAAM,OAAO,QAAQ,eAAe;EAW7C,QAAA,mBACP,UAAU,2BAAyB,KAAO,MAAI;EAMvC,OAAA,mBACP,UAAU,2BAAyB,KAAO,MAAI;EAMtC,QAAA,QAAA,YACR,UAAU,2BAAyB,KAAO,MAAI;EAMtC,SAAA,QAAA,YACR,UAAU,2BAAyB,KAAO,MAAI"}
|
package/dist/query.d.ts
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import { a as Query, c as QueryRef, d as queryIdentity, f as stableStringify, i as LivePolicy, l as QueryWindow, n as IDENTITY_SEPARATOR, o as QueryDescriptor, r as LiveInsertion, s as QueryInputs, t as ConnectionSpec, u as ResultOf } from "./query-BuIU93xA.js";
|
|
2
|
+
export { ConnectionSpec, IDENTITY_SEPARATOR, LiveInsertion, LivePolicy, Query, QueryDescriptor, QueryInputs, QueryRef, QueryWindow, ResultOf, queryIdentity, stableStringify };
|
package/dist/query.js
ADDED
|
Binary file
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"query.js","names":["RelationalQuery"],"sources":["../src/query.ts"],"sourcesContent":["/**\n * Queries and connections. A connection's identity is its descriptor plus the\n * canonical encoded filter/sort input — **never** the pagination window, so\n * `first(25)` and `after(cursor).first(25)` address the same logical connection.\n */\nimport { Schema } from 'effect'\nimport {\n Expr,\n Query as RelationalQuery,\n type AnyEntity,\n type AnyQuery,\n type InputExpr,\n} from 'foldkit-entity'\nimport type { Cursor } from './connection.js'\nimport { schemaOf, type SchemaOrFields, type TypeOf } from './mutation.js'\n\nexport type LiveInsertion = 'visible' | 'boundary' | 'invalidate' | 'ignore'\n\nexport interface LivePolicy {\n readonly prepend?: LiveInsertion\n readonly append?: LiveInsertion\n}\n\nexport interface ConnectionSpec<Entity extends string = string> {\n readonly entity: Entity\n readonly edgeKey?: Schema.Schema<unknown>\n readonly live?: LivePolicy\n}\n\nexport interface QueryWindow {\n readonly first?: number | undefined\n readonly last?: number | undefined\n readonly after?: Cursor | undefined\n readonly before?: Cursor | undefined\n}\n\nexport interface QueryRef<Name extends string, Input> {\n readonly query: Name\n readonly input: Input\n /** The input codec, so a `QueryRef` can encode itself without its descriptor. */\n readonly Input: Schema.Codec<Input>\n readonly window: QueryWindow\n /** Connection identity: descriptor + canonical input, excluding the window. */\n readonly identity: string\n}\n\nexport interface QueryDescriptor<Name extends string, Input, Result> {\n readonly name: Name\n readonly Input: Schema.Codec<Input>\n readonly Result: Result\n readonly ref: (input: Input) => QueryRef<Name, Input>\n /**\n * What the query means, when it was declared with `Query.define`: the rows it\n * is about, as a value an interpreter can compile. A descriptor from\n * `Query.make` has none — its meaning lives in whatever the server registered\n * to answer it — and a source is free to answer either.\n */\n readonly body?: AnyQuery | undefined\n}\n\n/**\n * The inputs of a definition's body: one placeholder per key of `Input`, typed\n * as that key's value. A body is built once, so these stand for what the query\n * will be given rather than being it.\n */\nexport type QueryInputs<Input> = {\n readonly [K in keyof Input & string]: InputExpr<Input[K]>\n}\n\n/**\n * What separates a query's name from its canonical encoded input in a\n * connection identity.\n *\n * NUL, because `JSON.stringify` escapes it inside a string (`\\u0000`) and a\n * query name is a TypeScript identifier — so the first occurrence is always the\n * boundary, and a name or an input value cannot forge one. Anything recovering\n * the query from an identity splits here.\n */\nexport const IDENTITY_SEPARATOR = '\\u0000'\n\n/** Stable stringify: object keys sorted, undefined-valued keys dropped, so equal inputs encode equally. */\nexport const stableStringify = (value: unknown): string => {\n if (value === undefined) return 'null'\n if (value === null || typeof value !== 'object') return JSON.stringify(value)\n if (Array.isArray(value)) return `[${value.map(stableStringify).join(',')}]`\n const record = value as Record<string, unknown>\n return `{${Object.keys(record)\n .filter(key => record[key] !== undefined)\n .sort()\n .map(key => `${JSON.stringify(key)}:${stableStringify(record[key])}`)\n .join(',')}}`\n}\n\n/**\n * A connection's identity: the query's name and its input, encoded and\n * canonical. What the client keys a list by, and what a server that runs a\n * query from its request (a live list) names the list as.\n */\nexport const queryIdentity = (query: string, encodedInput: unknown): string =>\n `${query}${IDENTITY_SEPARATOR}${stableStringify(encodedInput)}`\n\n/**\n * `Result: Project` (or anything with the entity's `name`, as `Query.connection`\n * takes) means a connection over it; a `ConnectionSpec` names its `entity`\n * instead and is the result as given.\n */\nexport type ResultOf<Result> = Result extends { readonly name: infer Entity extends string }\n ? ConnectionSpec<Entity>\n : Result\n\nconst isEntityName = (result: unknown): result is { readonly name: string } =>\n typeof result === 'object' &&\n result !== null &&\n typeof (result as { readonly name?: unknown }).name === 'string'\n\n/**\n * The relational half comes from `foldkit-entity`, so one `Query` namespace\n * holds both what a query means (`from`, `where`, `orderBy`) and how this\n * client addresses it (`make`, `connection`, the window steps). They are one\n * vocabulary in use — a definition's body is composed and then named — and two\n * namespaces of the same name in one file would be a trap.\n */\n/**\n * One placeholder per key of the input's schema, each carrying that key's own\n * codec so a comparison against it is checked.\n *\n * A codec that is not a `Schema.Struct` exposes no keys to stand for, while its\n * *type* may well have them — so the body would be handed an empty object,\n * `input.slug` would be `undefined`, and the comparison would silently be\n * against nothing while the types all agreed. That is a wrong query rather than\n * a missing one, so it is refused where the query is declared.\n */\nconst inputsOf = (\n name: string,\n schema: Schema.Codec<unknown>,\n): Record<string, InputExpr<unknown>> => {\n const fields = (schema as { readonly fields?: Schema.Struct.Fields }).fields\n if (fields === undefined) {\n throw new Error(\n `[foldkit-remote] Query.define: the Input of \"${name}\" is not a Schema.Struct, so its body has no inputs to read. Declare it as fields (\\`{ slug: Schema.String }\\`) or a plain \\`Schema.Struct\\`.`,\n )\n }\n const inputs: Record<string, InputExpr<unknown>> = {}\n for (const key of Object.keys(fields)) {\n inputs[key] = Expr.input(key, fields[key] as unknown as Schema.Codec<unknown, unknown>)\n }\n return Object.freeze(inputs)\n}\n\n/** A relational `Query` over one Entity, as `Query.define`'s body returns. */\ntype RelationalQueryOf<E extends AnyEntity> = ReturnType<typeof RelationalQuery.from<E>>\n\nexport const Query = {\n ...RelationalQuery,\n\n /** Declares the entity and options a query's result is a connection over. */\n connection: <Entity extends string>(\n entity: { readonly name: Entity },\n options?: { readonly edgeKey?: Schema.Schema<unknown>; readonly live?: LivePolicy },\n ): ConnectionSpec<Entity> => ({\n entity: entity.name,\n ...(options?.edgeKey === undefined ? {} : { edgeKey: options.edgeKey }),\n ...(options?.live === undefined ? {} : { live: options.live }),\n }),\n\n /**\n * Declares a query. `Input` is a codec or the fields of the `Schema.Struct` it\n * would be; `Result` is a `Query.connection(...)`, or the entity the result is\n * a connection over.\n */\n make: <const Name extends string, Input extends SchemaOrFields, Result>(\n name: Name,\n config: { readonly Input: Input; readonly Result: Result },\n ): QueryDescriptor<Name, TypeOf<Input>, ResultOf<Result>> => {\n const Input = schemaOf(config.Input)\n const encode = Schema.encodeSync(Input)\n return {\n name,\n Input,\n Result: (isEntityName(config.Result)\n ? Query.connection(config.Result)\n : config.Result) as ResultOf<Result>,\n ref: input => ({\n query: name,\n input,\n Input,\n window: {},\n identity: queryIdentity(name, encode(input)),\n }),\n }\n },\n\n /**\n * Declares a query by what it means. The body is built once, here, and the\n * `input` it is given holds placeholders rather than values — there is\n * nothing yet to branch on, so a condition that depends on what was passed is\n * a comparison over the placeholder:\n *\n * ```ts\n * const PostsBySlug = Query.define('PostsBySlug', { slug: Schema.String }, ({ input }) =>\n * Query.from(Post).pipe(\n * Query.where(Expr.eq(Post.fields.slug, input.slug)),\n * Query.orderBy(Order.asc(Post.fields.id)),\n * ),\n * )\n * ```\n *\n * What comes back is an ordinary `QueryDescriptor` — the same name, `Input`,\n * `ref` and connection identity `Query.make` gives — carrying its `body`\n * besides. A server can compile that body instead of being told the same\n * thing again in its own dialect, and one that would rather answer the query\n * its own way still can.\n *\n * The result is a connection over the Entity the body reads, so it is not\n * named twice.\n */\n define: <const Name extends string, Input extends SchemaOrFields, E extends AnyEntity>(\n name: Name,\n Input: Input,\n body: (context: { readonly input: QueryInputs<TypeOf<Input>> }) => RelationalQueryOf<E>,\n options?: { readonly edgeKey?: Schema.Schema<unknown>; readonly live?: LivePolicy },\n ): QueryDescriptor<Name, TypeOf<Input>, ConnectionSpec<E['name']>> => {\n const schema = schemaOf(Input)\n const built = body({ input: inputsOf(name, schema) as QueryInputs<TypeOf<Input>> })\n const descriptor = Query.make(name, {\n Input,\n Result: Query.connection(built.entity, options) as ConnectionSpec<E['name']>,\n })\n return { ...descriptor, body: built }\n },\n\n first:\n (count: number) =>\n <R extends QueryRef<string, unknown>>(ref: R): R => ({\n ...ref,\n window: { ...ref.window, first: count },\n }),\n\n last:\n (count: number) =>\n <R extends QueryRef<string, unknown>>(ref: R): R => ({\n ...ref,\n window: { ...ref.window, last: count },\n }),\n\n after:\n (cursor: Cursor) =>\n <R extends QueryRef<string, unknown>>(ref: R): R => ({\n ...ref,\n window: { ...ref.window, after: cursor },\n }),\n\n before:\n (cursor: Cursor) =>\n <R extends QueryRef<string, unknown>>(ref: R): R => ({\n ...ref,\n window: { ...ref.window, before: cursor },\n }),\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AA8EA,MAAa,qBAAqB;;AAGlC,MAAa,mBAAmB,UAA2B;CACzD,IAAI,UAAU,KAAA,GAAW,OAAO;CAChC,IAAI,UAAU,QAAQ,OAAO,UAAU,UAAU,OAAO,KAAK,UAAU,KAAK;CAC5E,IAAI,MAAM,QAAQ,KAAK,GAAG,OAAO,IAAI,MAAM,IAAI,eAAe,CAAC,CAAC,KAAK,GAAG,EAAE;CAC1E,MAAM,SAAS;CACf,OAAO,IAAI,OAAO,KAAK,MAAM,CAAC,CAC3B,QAAO,QAAO,OAAO,SAAS,KAAA,CAAS,CAAC,CACxC,KAAK,CAAC,CACN,KAAI,QAAO,GAAG,KAAK,UAAU,GAAG,EAAE,GAAG,gBAAgB,OAAO,IAAI,GAAG,CAAC,CACpE,KAAK,GAAG,EAAE;AACf;;;;;;AAOA,MAAa,iBAAiB,OAAe,iBAC3C,GAAG,SAA6B,gBAAgB,YAAY;AAW9D,MAAM,gBAAgB,WACpB,OAAO,WAAW,YAClB,WAAW,QACX,OAAQ,OAAuC,SAAS;;;;;;;;;;;;;;;;;;AAmB1D,MAAM,YACJ,MACA,WACuC;CACvC,MAAM,SAAU,OAAsD;CACtE,IAAI,WAAW,KAAA,GACb,MAAM,IAAI,MACR,gDAAgD,KAAK,8IACvD;CAEF,MAAM,SAA6C,CAAC;CACpD,KAAK,MAAM,OAAO,OAAO,KAAK,MAAM,GAClC,OAAO,OAAO,KAAK,MAAM,KAAK,OAAO,IAAiD;CAExF,OAAO,OAAO,OAAO,MAAM;AAC7B;AAKA,MAAa,QAAQ;CACnB,GAAGA;;CAGH,aACE,QACA,aAC4B;EAC5B,QAAQ,OAAO;EACf,GAAI,SAAS,YAAY,KAAA,IAAY,CAAC,IAAI,EAAE,SAAS,QAAQ,QAAQ;EACrE,GAAI,SAAS,SAAS,KAAA,IAAY,CAAC,IAAI,EAAE,MAAM,QAAQ,KAAK;CAC9D;;;;;;CAOA,OACE,MACA,WAC2D;EAC3D,MAAM,QAAQ,SAAS,OAAO,KAAK;EACnC,MAAM,SAAS,OAAO,WAAW,KAAK;EACtC,OAAO;GACL;GACA;GACA,QAAS,aAAa,OAAO,MAAM,IAC/B,MAAM,WAAW,OAAO,MAAM,IAC9B,OAAO;GACX,MAAK,WAAU;IACb,OAAO;IACP;IACA;IACA,QAAQ,CAAC;IACT,UAAU,cAAc,MAAM,OAAO,KAAK,CAAC;GAC7C;EACF;CACF;;;;;;;;;;;;;;;;;;;;;;;;;CA0BA,SACE,MACA,OACA,MACA,YACoE;EACpE,MAAM,SAAS,SAAS,KAAK;EAC7B,MAAM,QAAQ,KAAK,EAAE,OAAO,SAAS,MAAM,MAAM,EAAgC,CAAC;EAKlF,OAAO;GAAE,GAJU,MAAM,KAAK,MAAM;IAClC;IACA,QAAQ,MAAM,WAAW,MAAM,QAAQ,OAAO;GAChD,CACqB;GAAG,MAAM;EAAM;CACtC;CAEA,QACG,WACqC,SAAe;EACnD,GAAG;EACH,QAAQ;GAAE,GAAG,IAAI;GAAQ,OAAO;EAAM;CACxC;CAEF,OACG,WACqC,SAAe;EACnD,GAAG;EACH,QAAQ;GAAE,GAAG,IAAI;GAAQ,MAAM;EAAM;CACvC;CAEF,QACG,YACqC,SAAe;EACnD,GAAG;EACH,QAAQ;GAAE,GAAG,IAAI;GAAQ,OAAO;EAAO;CACzC;CAEF,SACG,YACqC,SAAe;EACnD,GAAG;EACH,QAAQ;GAAE,GAAG,IAAI;GAAQ,QAAQ;EAAO;CAC1C;AACJ"}
|
package/dist/record.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Records keyed by a field name, which an application declares and the wire
|
|
3
|
+
* repeats. Read as `record[key]`, `constructor` or `toString` finds
|
|
4
|
+
* `Object.prototype`'s; written as `record[key] = value`, `__proto__` sets the
|
|
5
|
+
* prototype and adds no key.
|
|
6
|
+
*/
|
|
7
|
+
/** The value `record` holds under `key` itself, never one it inherits. */
|
|
8
|
+
export declare const own: <A>(record: Readonly<Record<string, A>> | undefined, key: string) => A | undefined;
|
|
9
|
+
/** Sets `key` on `record` as a key of its own, whatever the name. */
|
|
10
|
+
export declare const put: <A>(record: Record<string, A>, key: string, value: A) => void;
|
|
11
|
+
//# sourceMappingURL=record.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"record.d.ts","names":[],"sources":["../src/record.ts"],"mappings":";;;;;;;qBAQa,MAAO,GAAC,QACX,SAAS,eAAe,iBAAe,gBAE9C;;qBAGU,MAAO,GAAC,QAAU,eAAe,IAAE,aAAa,OAAS"}
|
package/dist/record.js
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// src/record.ts
|
|
2
|
+
/**
|
|
3
|
+
* Records keyed by a field name, which an application declares and the wire
|
|
4
|
+
* repeats. Read as `record[key]`, `constructor` or `toString` finds
|
|
5
|
+
* `Object.prototype`'s; written as `record[key] = value`, `__proto__` sets the
|
|
6
|
+
* prototype and adds no key.
|
|
7
|
+
*/
|
|
8
|
+
/** The value `record` holds under `key` itself, never one it inherits. */
|
|
9
|
+
const own = (record, key) => record !== void 0 && Object.hasOwn(record, key) ? record[key] : void 0;
|
|
10
|
+
/** Sets `key` on `record` as a key of its own, whatever the name. */
|
|
11
|
+
const put = (record, key, value) => {
|
|
12
|
+
Object.defineProperty(record, key, {
|
|
13
|
+
value,
|
|
14
|
+
writable: true,
|
|
15
|
+
enumerable: true,
|
|
16
|
+
configurable: true
|
|
17
|
+
});
|
|
18
|
+
};
|
|
19
|
+
export { own, put };
|
|
20
|
+
|
|
21
|
+
//# sourceMappingURL=record.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"record.js","names":[],"sources":["../src/record.ts"],"sourcesContent":["/**\n * Records keyed by a field name, which an application declares and the wire\n * repeats. Read as `record[key]`, `constructor` or `toString` finds\n * `Object.prototype`'s; written as `record[key] = value`, `__proto__` sets the\n * prototype and adds no key.\n */\n\n/** The value `record` holds under `key` itself, never one it inherits. */\nexport const own = <A>(\n record: Readonly<Record<string, A>> | undefined,\n key: string,\n): A | undefined => (record !== undefined && Object.hasOwn(record, key) ? record[key] : undefined)\n\n/** Sets `key` on `record` as a key of its own, whatever the name. */\nexport const put = <A>(record: Record<string, A>, key: string, value: A): void => {\n Object.defineProperty(record, key, {\n value,\n writable: true,\n enumerable: true,\n configurable: true,\n })\n}\n"],"mappings":";;;;;;;;AAQA,MAAa,OACX,QACA,QACmB,WAAW,KAAA,KAAa,OAAO,OAAO,QAAQ,GAAG,IAAI,OAAO,OAAO,KAAA;;AAGxF,MAAa,OAAU,QAA2B,KAAa,UAAmB;CAChF,OAAO,eAAe,QAAQ,KAAK;EACjC;EACA,UAAU;EACV,YAAY;EACZ,cAAc;CAChB,CAAC;AACH"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { n as RelationRequirement } from "./requirement-D57S2LpH.js";
|
|
2
|
+
import { Schema } from "effect";
|
|
3
|
+
interface RefParts {
|
|
4
|
+
readonly entity: string;
|
|
5
|
+
readonly id: string;
|
|
6
|
+
}
|
|
7
|
+
/** A page of refs as the wire and the store hold it. */
|
|
8
|
+
interface RefPageValue {
|
|
9
|
+
readonly refs: ReadonlyArray<string>;
|
|
10
|
+
readonly hasNext: boolean;
|
|
11
|
+
readonly hasPrevious: boolean;
|
|
12
|
+
}
|
|
13
|
+
/** Marks the codec `Entity.ref`/`Entity.refPage` produce, so a Selection can tell the field's shape. */
|
|
14
|
+
declare const RelationAnnotation = "foldkitRemoteRelation";
|
|
15
|
+
/** The target entity's name on the same codec, so a nested selection of another entity is refused. */
|
|
16
|
+
declare const RelationEntityAnnotation = "foldkitRemoteRelationEntity";
|
|
17
|
+
type RelationKind = 'one' | 'many' | 'page';
|
|
18
|
+
interface RelationShape {
|
|
19
|
+
readonly kind: RelationKind;
|
|
20
|
+
readonly nullable: boolean;
|
|
21
|
+
readonly entity: string;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* A page of a relation that is a whole list is read under a name of its own, the
|
|
25
|
+
* field's name and the page's size: `comments@first=10`. It is a field like any
|
|
26
|
+
* other to the store, the planner, and the wire, so the whole list and a page of
|
|
27
|
+
* it are held side by side, merged, marked stale, and persisted each on its own.
|
|
28
|
+
* The server reads the name apart, reads the relation with the window, and
|
|
29
|
+
* answers under the alias. A cursor is not part of the name: a page read from a
|
|
30
|
+
* cursor merges onto the page it continues.
|
|
31
|
+
*/
|
|
32
|
+
declare const RELATION_ALIAS = "@";
|
|
33
|
+
/** The alias a relation field is read under, for a window of this size. */
|
|
34
|
+
declare const relationAlias: (field: string, window: {
|
|
35
|
+
readonly first?: number | undefined;
|
|
36
|
+
readonly last?: number | undefined;
|
|
37
|
+
readonly after?: string | undefined;
|
|
38
|
+
readonly before?: string | undefined;
|
|
39
|
+
}) => string;
|
|
40
|
+
/** The field an alias reads; a name that is no alias is its own field. */
|
|
41
|
+
declare const aliasedField: (name: string) => string;
|
|
42
|
+
/** Splits a ref key (`"Entity:id"`, the store key too) back into its parts. */
|
|
43
|
+
declare const refParts: (encoded: string) => RefParts;
|
|
44
|
+
/**
|
|
45
|
+
* The refs a relation value carries, in order. Anything that is not a ref key,
|
|
46
|
+
* an array of them, or a page of them contributes nothing.
|
|
47
|
+
*/
|
|
48
|
+
declare const refsIn: (value: unknown) => ReadonlyArray<RefParts>;
|
|
49
|
+
/** The refs in a relation value that name the relation's target entity, and no other. */
|
|
50
|
+
declare const targetsOf: (value: unknown, relation: RelationRequirement) => ReadonlyArray<RefParts>;
|
|
51
|
+
declare const isRefPage: (value: unknown) => value is RefPageValue;
|
|
52
|
+
/**
|
|
53
|
+
* The relation shape an entity field schema declares: a ref (`one`), a nullable
|
|
54
|
+
* ref, an array of refs (`many`), or a page of refs (`page`). `undefined` for a
|
|
55
|
+
* scalar field, which cannot take a nested selection.
|
|
56
|
+
*/
|
|
57
|
+
declare const relationShape: (schema: Schema.Constraint) => RelationShape | undefined;
|
|
58
|
+
export { RelationEntityAnnotation as a, aliasedField as c, refsIn as d, relationAlias as f, RelationAnnotation as i, isRefPage as l, targetsOf as m, RefPageValue as n, RelationKind as o, relationShape as p, RefParts as r, RelationShape as s, RELATION_ALIAS as t, refParts as u };
|
|
59
|
+
//# sourceMappingURL=relation-BLBGOZom.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"relation-BLBGOZom.d.ts","names":[],"sources":["../src/relation.ts"],"mappings":";;UASiB;WACN;WACA;;;UAIM;WACN,MAAM;WACN;WACA;;;cAIE;;cAEA;KAED;UAEK;WACN,MAAM;WACN;WACA;;;;;;;;;;;cAYE;;cAGA,gBAAa,eACX;WAEF;WACA;WAEA;WACA;;;cAMA,eAAY;;cAMZ,WAAQ,oBAAsB;;;;;cAW9B,SAAM,mBAAqB,cAAc;;cAWzC,YAAS,gBAAkB,UAAY,wBAAsB,cAAc;cAG3E,YAAS,mBAAqB,SAAS;;;;;;cAgBvC,gBAAa,QAAY,OAAO,eAAa"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import { a as RelationEntityAnnotation, c as aliasedField, d as refsIn, f as relationAlias, i as RelationAnnotation, l as isRefPage, m as targetsOf, n as RefPageValue, o as RelationKind, p as relationShape, r as RefParts, s as RelationShape, t as RELATION_ALIAS, u as refParts } from "./relation-BLBGOZom.js";
|
|
2
|
+
export { RELATION_ALIAS, RefPageValue, RefParts, RelationAnnotation, RelationEntityAnnotation, RelationKind, RelationShape, aliasedField, isRefPage, refParts, refsIn, relationAlias, relationShape, targetsOf };
|
package/dist/relation.js
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { SchemaAST } from "effect";
|
|
2
|
+
import { SchemaShape } from "foldkit-entity";
|
|
3
|
+
// src/relation.ts
|
|
4
|
+
/**
|
|
5
|
+
* Relation values on the wire and in the store: a ref key (`"Entity:id"`), a
|
|
6
|
+
* nullable ref key, an array of ref keys, or a page of ref keys. The planner
|
|
7
|
+
* follows them into the store; the server follows them into the next read.
|
|
8
|
+
*/
|
|
9
|
+
/** Marks the codec `Entity.ref`/`Entity.refPage` produce, so a Selection can tell the field's shape. */
|
|
10
|
+
const RelationAnnotation = "foldkitRemoteRelation";
|
|
11
|
+
/** The target entity's name on the same codec, so a nested selection of another entity is refused. */
|
|
12
|
+
const RelationEntityAnnotation = "foldkitRemoteRelationEntity";
|
|
13
|
+
/**
|
|
14
|
+
* A page of a relation that is a whole list is read under a name of its own, the
|
|
15
|
+
* field's name and the page's size: `comments@first=10`. It is a field like any
|
|
16
|
+
* other to the store, the planner, and the wire, so the whole list and a page of
|
|
17
|
+
* it are held side by side, merged, marked stale, and persisted each on its own.
|
|
18
|
+
* The server reads the name apart, reads the relation with the window, and
|
|
19
|
+
* answers under the alias. A cursor is not part of the name: a page read from a
|
|
20
|
+
* cursor merges onto the page it continues.
|
|
21
|
+
*/
|
|
22
|
+
const RELATION_ALIAS = "@";
|
|
23
|
+
/** The alias a relation field is read under, for a window of this size. */
|
|
24
|
+
const relationAlias = (field, window) => `${field}@${window.last !== void 0 ? `last=${window.last}` : `first=${window.first ?? ""}`}`;
|
|
25
|
+
/** The field an alias reads; a name that is no alias is its own field. */
|
|
26
|
+
const aliasedField = (name) => {
|
|
27
|
+
const at = name.indexOf("@");
|
|
28
|
+
return at === -1 ? name : name.slice(0, at);
|
|
29
|
+
};
|
|
30
|
+
/** Splits a ref key (`"Entity:id"`, the store key too) back into its parts. */
|
|
31
|
+
const refParts = (encoded) => {
|
|
32
|
+
const separator = encoded.indexOf(":");
|
|
33
|
+
return separator === -1 ? {
|
|
34
|
+
entity: encoded,
|
|
35
|
+
id: ""
|
|
36
|
+
} : {
|
|
37
|
+
entity: encoded.slice(0, separator),
|
|
38
|
+
id: encoded.slice(separator + 1)
|
|
39
|
+
};
|
|
40
|
+
};
|
|
41
|
+
/**
|
|
42
|
+
* The refs a relation value carries, in order. Anything that is not a ref key,
|
|
43
|
+
* an array of them, or a page of them contributes nothing.
|
|
44
|
+
*/
|
|
45
|
+
const refsIn = (value) => {
|
|
46
|
+
if (typeof value === "string") return [refParts(value)];
|
|
47
|
+
if (Array.isArray(value)) return value.filter((item) => typeof item === "string").map(refParts);
|
|
48
|
+
if (value !== null && typeof value === "object") {
|
|
49
|
+
const refs = value.refs;
|
|
50
|
+
if (Array.isArray(refs)) return refs.filter((item) => typeof item === "string").map(refParts);
|
|
51
|
+
}
|
|
52
|
+
return [];
|
|
53
|
+
};
|
|
54
|
+
/** The refs in a relation value that name the relation's target entity, and no other. */
|
|
55
|
+
const targetsOf = (value, relation) => refsIn(value).filter((ref) => ref.entity === relation.entity);
|
|
56
|
+
const isRefPage = (value) => {
|
|
57
|
+
if (value === null || typeof value !== "object") return false;
|
|
58
|
+
const page = value;
|
|
59
|
+
return Array.isArray(page.refs) && page.refs.every((ref) => typeof ref === "string") && typeof page.hasNext === "boolean" && typeof page.hasPrevious === "boolean";
|
|
60
|
+
};
|
|
61
|
+
/**
|
|
62
|
+
* The relation shape an entity field schema declares: a ref (`one`), a nullable
|
|
63
|
+
* ref, an array of refs (`many`), or a page of refs (`page`). `undefined` for a
|
|
64
|
+
* scalar field, which cannot take a nested selection.
|
|
65
|
+
*/
|
|
66
|
+
const relationShape = (schema) => shapeOf(schema.ast, false);
|
|
67
|
+
const shapeOf = (ast, nullable) => {
|
|
68
|
+
const annotated = ast.annotations?.[RelationAnnotation];
|
|
69
|
+
if (annotated === "one" || annotated === "page") return {
|
|
70
|
+
kind: annotated,
|
|
71
|
+
nullable,
|
|
72
|
+
entity: String(ast.annotations?.[RelationEntityAnnotation])
|
|
73
|
+
};
|
|
74
|
+
if (SchemaAST.isArrays(ast)) {
|
|
75
|
+
const item = ast.rest[0];
|
|
76
|
+
const inner = item === void 0 ? void 0 : shapeOf(item, false);
|
|
77
|
+
return inner?.kind === "one" ? {
|
|
78
|
+
kind: "many",
|
|
79
|
+
nullable,
|
|
80
|
+
entity: inner.entity
|
|
81
|
+
} : void 0;
|
|
82
|
+
}
|
|
83
|
+
if (SchemaAST.isUnion(ast)) {
|
|
84
|
+
const optional = ast.types.some(SchemaShape.isNullish);
|
|
85
|
+
for (const member of ast.types) {
|
|
86
|
+
const shape = shapeOf(member, optional);
|
|
87
|
+
if (shape !== void 0) return shape;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
export { RELATION_ALIAS, RelationAnnotation, RelationEntityAnnotation, aliasedField, isRefPage, refParts, refsIn, relationAlias, relationShape, targetsOf };
|
|
92
|
+
|
|
93
|
+
//# sourceMappingURL=relation.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"relation.js","names":[],"sources":["../src/relation.ts"],"sourcesContent":["/**\n * Relation values on the wire and in the store: a ref key (`\"Entity:id\"`), a\n * nullable ref key, an array of ref keys, or a page of ref keys. The planner\n * follows them into the store; the server follows them into the next read.\n */\nimport { SchemaAST, type Schema } from 'effect'\nimport { SchemaShape } from 'foldkit-entity'\nimport type { RelationRequirement } from './requirement.js'\n\nexport interface RefParts {\n readonly entity: string\n readonly id: string\n}\n\n/** A page of refs as the wire and the store hold it. */\nexport interface RefPageValue {\n readonly refs: ReadonlyArray<string>\n readonly hasNext: boolean\n readonly hasPrevious: boolean\n}\n\n/** Marks the codec `Entity.ref`/`Entity.refPage` produce, so a Selection can tell the field's shape. */\nexport const RelationAnnotation = 'foldkitRemoteRelation'\n/** The target entity's name on the same codec, so a nested selection of another entity is refused. */\nexport const RelationEntityAnnotation = 'foldkitRemoteRelationEntity'\n\nexport type RelationKind = 'one' | 'many' | 'page'\n\nexport interface RelationShape {\n readonly kind: RelationKind\n readonly nullable: boolean\n readonly entity: string\n}\n\n/**\n * A page of a relation that is a whole list is read under a name of its own, the\n * field's name and the page's size: `comments@first=10`. It is a field like any\n * other to the store, the planner, and the wire, so the whole list and a page of\n * it are held side by side, merged, marked stale, and persisted each on its own.\n * The server reads the name apart, reads the relation with the window, and\n * answers under the alias. A cursor is not part of the name: a page read from a\n * cursor merges onto the page it continues.\n */\nexport const RELATION_ALIAS = '@'\n\n/** The alias a relation field is read under, for a window of this size. */\nexport const relationAlias = (\n field: string,\n window: {\n readonly first?: number | undefined\n readonly last?: number | undefined\n // A cursor is no part of the name: a page read from one merges onto the page it continues.\n readonly after?: string | undefined\n readonly before?: string | undefined\n },\n): string =>\n `${field}${RELATION_ALIAS}${window.last !== undefined ? `last=${window.last}` : `first=${window.first ?? ''}`}`\n\n/** The field an alias reads; a name that is no alias is its own field. */\nexport const aliasedField = (name: string): string => {\n const at = name.indexOf(RELATION_ALIAS)\n return at === -1 ? name : name.slice(0, at)\n}\n\n/** Splits a ref key (`\"Entity:id\"`, the store key too) back into its parts. */\nexport const refParts = (encoded: string): RefParts => {\n const separator = encoded.indexOf(':')\n return separator === -1\n ? { entity: encoded, id: '' }\n : { entity: encoded.slice(0, separator), id: encoded.slice(separator + 1) }\n}\n\n/**\n * The refs a relation value carries, in order. Anything that is not a ref key,\n * an array of them, or a page of them contributes nothing.\n */\nexport const refsIn = (value: unknown): ReadonlyArray<RefParts> => {\n if (typeof value === 'string') return [refParts(value)]\n if (Array.isArray(value)) return value.filter(item => typeof item === 'string').map(refParts)\n if (value !== null && typeof value === 'object') {\n const refs = (value as { readonly refs?: unknown }).refs\n if (Array.isArray(refs)) return refs.filter(item => typeof item === 'string').map(refParts)\n }\n return []\n}\n\n/** The refs in a relation value that name the relation's target entity, and no other. */\nexport const targetsOf = (value: unknown, relation: RelationRequirement): ReadonlyArray<RefParts> =>\n refsIn(value).filter(ref => ref.entity === relation.entity)\n\nexport const isRefPage = (value: unknown): value is RefPageValue => {\n if (value === null || typeof value !== 'object') return false\n const page = value as Record<string, unknown>\n return (\n Array.isArray(page.refs) &&\n page.refs.every(ref => typeof ref === 'string') &&\n typeof page.hasNext === 'boolean' &&\n typeof page.hasPrevious === 'boolean'\n )\n}\n\n/**\n * The relation shape an entity field schema declares: a ref (`one`), a nullable\n * ref, an array of refs (`many`), or a page of refs (`page`). `undefined` for a\n * scalar field, which cannot take a nested selection.\n */\nexport const relationShape = (schema: Schema.Constraint): RelationShape | undefined =>\n shapeOf(schema.ast, false)\n\nconst shapeOf = (ast: SchemaAST.AST, nullable: boolean): RelationShape | undefined => {\n const annotated = ast.annotations?.[RelationAnnotation]\n if (annotated === 'one' || annotated === 'page') {\n return {\n kind: annotated,\n nullable,\n entity: String(ast.annotations?.[RelationEntityAnnotation]),\n }\n }\n if (SchemaAST.isArrays(ast)) {\n const item = ast.rest[0]\n const inner = item === undefined ? undefined : shapeOf(item, false)\n return inner?.kind === 'one' ? { kind: 'many', nullable, entity: inner.entity } : undefined\n }\n if (SchemaAST.isUnion(ast)) {\n const optional = ast.types.some(SchemaShape.isNullish)\n for (const member of ast.types) {\n const shape = shapeOf(member, optional)\n if (shape !== undefined) return shape\n }\n }\n return undefined\n}\n"],"mappings":";;;;;;;;;AAsBA,MAAa,qBAAqB;;AAElC,MAAa,2BAA2B;;;;;;;;;;AAmBxC,MAAa,iBAAiB;;AAG9B,MAAa,iBACX,OACA,WAQA,GAAG,SAAyB,OAAO,SAAS,KAAA,IAAY,QAAQ,OAAO,SAAS,SAAS,OAAO,SAAS;;AAG3G,MAAa,gBAAgB,SAAyB;CACpD,MAAM,KAAK,KAAK,QAAA,GAAsB;CACtC,OAAO,OAAO,KAAK,OAAO,KAAK,MAAM,GAAG,EAAE;AAC5C;;AAGA,MAAa,YAAY,YAA8B;CACrD,MAAM,YAAY,QAAQ,QAAQ,GAAG;CACrC,OAAO,cAAc,KACjB;EAAE,QAAQ;EAAS,IAAI;CAAG,IAC1B;EAAE,QAAQ,QAAQ,MAAM,GAAG,SAAS;EAAG,IAAI,QAAQ,MAAM,YAAY,CAAC;CAAE;AAC9E;;;;;AAMA,MAAa,UAAU,UAA4C;CACjE,IAAI,OAAO,UAAU,UAAU,OAAO,CAAC,SAAS,KAAK,CAAC;CACtD,IAAI,MAAM,QAAQ,KAAK,GAAG,OAAO,MAAM,QAAO,SAAQ,OAAO,SAAS,QAAQ,CAAC,CAAC,IAAI,QAAQ;CAC5F,IAAI,UAAU,QAAQ,OAAO,UAAU,UAAU;EAC/C,MAAM,OAAQ,MAAsC;EACpD,IAAI,MAAM,QAAQ,IAAI,GAAG,OAAO,KAAK,QAAO,SAAQ,OAAO,SAAS,QAAQ,CAAC,CAAC,IAAI,QAAQ;CAC5F;CACA,OAAO,CAAC;AACV;;AAGA,MAAa,aAAa,OAAgB,aACxC,OAAO,KAAK,CAAC,CAAC,QAAO,QAAO,IAAI,WAAW,SAAS,MAAM;AAE5D,MAAa,aAAa,UAA0C;CAClE,IAAI,UAAU,QAAQ,OAAO,UAAU,UAAU,OAAO;CACxD,MAAM,OAAO;CACb,OACE,MAAM,QAAQ,KAAK,IAAI,KACvB,KAAK,KAAK,OAAM,QAAO,OAAO,QAAQ,QAAQ,KAC9C,OAAO,KAAK,YAAY,aACxB,OAAO,KAAK,gBAAgB;AAEhC;;;;;;AAOA,MAAa,iBAAiB,WAC5B,QAAQ,OAAO,KAAK,KAAK;AAE3B,MAAM,WAAW,KAAoB,aAAiD;CACpF,MAAM,YAAY,IAAI,cAAc;CACpC,IAAI,cAAc,SAAS,cAAc,QACvC,OAAO;EACL,MAAM;EACN;EACA,QAAQ,OAAO,IAAI,cAAc,yBAAyB;CAC5D;CAEF,IAAI,UAAU,SAAS,GAAG,GAAG;EAC3B,MAAM,OAAO,IAAI,KAAK;EACtB,MAAM,QAAQ,SAAS,KAAA,IAAY,KAAA,IAAY,QAAQ,MAAM,KAAK;EAClE,OAAO,OAAO,SAAS,QAAQ;GAAE,MAAM;GAAQ;GAAU,QAAQ,MAAM;EAAO,IAAI,KAAA;CACpF;CACA,IAAI,UAAU,QAAQ,GAAG,GAAG;EAC1B,MAAM,WAAW,IAAI,MAAM,KAAK,YAAY,SAAS;EACrD,KAAK,MAAM,UAAU,IAAI,OAAO;GAC9B,MAAM,QAAQ,QAAQ,QAAQ,QAAQ;GACtC,IAAI,UAAU,KAAA,GAAW,OAAO;EAClC;CACF;AAEF"}
|