Absorb the useShape contract change the provider versions require
@ng-org/orm stopped returning the DeepSignalSet from useShape and now returns a result object carrying it under data. The bump is forced: the providers import RdfOrmSubscription, which alpha.18 published as OrmSubscription. useShapeSet unwraps it in one place and hands back a set, so the call sites keep the contract they were written against. While the subscription is loading it yields an empty set that refuses writes rather than dropping them, matching what the previous version documented.
This commit is contained in:
@@ -0,0 +1,57 @@
|
||||
/**
|
||||
* useShapeSet — `useShape` narrowed back down to "just the set".
|
||||
*
|
||||
* WHY THIS EXISTS. `@ng-org/orm` changed `useShape`'s contract between alpha.18
|
||||
* and alpha.21: it used to RETURN the `DeepSignalSet`, and now returns a result
|
||||
* object that carries it under `data`, alongside `isLoading`, `promise`,
|
||||
* `subscription` and the paging controls. Every Festipod call site wants the set,
|
||||
* so the whole adaptation lives HERE, in one place, rather than being spelled out
|
||||
* at each `useShape(...)`.
|
||||
*
|
||||
* AND THE BUMP IS NOT OPTIONAL. `@ng-org/refine-providers` imports
|
||||
* `RdfOrmSubscription` from `@ng-org/orm`; alpha.18 published that class under
|
||||
* the name `OrmSubscription` and only alpha.21 exports `RdfOrmSubscription`. So
|
||||
* using the providers requires alpha.21, which requires absorbing this break.
|
||||
*
|
||||
* WHILE LOADING, `data` is `undefined`. Callers get an empty set instead — and a
|
||||
* set that REFUSES writes, which is what alpha.18 handed back for an undefined
|
||||
* scope: *"an empty set is returned which errors if you try to make modifications
|
||||
* on it"*. Accepting the write instead would drop it on the floor, silently, in
|
||||
* exactly the window where a caller is most likely to try.
|
||||
*/
|
||||
|
||||
import { useMemo } from 'react';
|
||||
import { useShape } from '@ng-org/orm/react';
|
||||
import type { ShapeType, BaseType } from '@ng-org/shex-orm';
|
||||
import type { DeepSignalSet } from '@ng-org/alien-deepsignals';
|
||||
|
||||
/**
|
||||
* `scope` is either a single store/document NURI (mono-store mode) or a
|
||||
* `{ graphs }` set of document NURIs (multi-document mode — storeRegistry).
|
||||
* `useShape` accepts both natively.
|
||||
*/
|
||||
export type ShapeScope = string | { graphs: string[] } | undefined;
|
||||
|
||||
/** An empty set that names the problem instead of swallowing a write. */
|
||||
function notLoadedSet<T>(): DeepSignalSet<T> {
|
||||
const refuse = (): never => {
|
||||
throw new Error(
|
||||
'useShapeSet: the shape subscription has not loaded yet — read it, do not write to it',
|
||||
);
|
||||
};
|
||||
return Object.assign(new Set<T>(), { add: refuse, delete: refuse }) as unknown as DeepSignalSet<T>;
|
||||
}
|
||||
|
||||
export function useShapeSet<NgT extends BaseType>(
|
||||
shapeType: ShapeType<NgT>,
|
||||
scope: ShapeScope,
|
||||
): DeepSignalSet<NgT> {
|
||||
// Memoised so the placeholder keeps ONE identity for this hook instance: it
|
||||
// lands in `useCallback` dependency arrays, and a fresh set on every render
|
||||
// would rebuild every mutation callback for as long as the load takes.
|
||||
const placeholder = useMemo(() => notLoadedSet<NgT>(), []);
|
||||
const result = useShape(shapeType as never, scope as never) as unknown as {
|
||||
data?: DeepSignalSet<NgT>;
|
||||
};
|
||||
return result?.data ?? placeholder;
|
||||
}
|
||||
@@ -8,9 +8,10 @@
|
||||
* Must only be called when NG is connected (inside NgDataProvider).
|
||||
*/
|
||||
|
||||
import { useShape } from '@ng-org/orm/react';
|
||||
import type { ShapeType, BaseType } from '@ng-org/shex-orm';
|
||||
import type { DeepSignalSet } from '@ng-org/alien-deepsignals';
|
||||
import { useShapeSet, type ShapeScope } from './useShapeSet';
|
||||
|
||||
export interface ShapeWithDefaults<NgT extends BaseType, AppT> {
|
||||
/** Mapped items from NG store */
|
||||
items: AppT[];
|
||||
@@ -18,12 +19,9 @@ export interface ShapeWithDefaults<NgT extends BaseType, AppT> {
|
||||
ngSet: DeepSignalSet<NgT>;
|
||||
}
|
||||
|
||||
/**
|
||||
* `scope` is either a single store/document NURI (mono-store mode) or a
|
||||
* `{ graphs }` set of document NURIs (multi-document mode — storeRegistry).
|
||||
* `useShape` accepts both natively.
|
||||
*/
|
||||
export type ShapeScope = string | { graphs: string[] } | undefined;
|
||||
// `ShapeScope` now belongs to useShapeSet (the module that talks to the ORM);
|
||||
// it is re-exported here so the existing importers stay unchanged.
|
||||
export type { ShapeScope };
|
||||
|
||||
export function useShapeWithDefaults<NgT extends BaseType, AppT>(
|
||||
shapeType: ShapeType<NgT>,
|
||||
@@ -34,7 +32,8 @@ export function useShapeWithDefaults<NgT extends BaseType, AppT>(
|
||||
): ShapeWithDefaults<NgT, AppT> {
|
||||
// Mono-store: a single store NURI opens the repo in the verifier (enables
|
||||
// writes). Multi-document: a { graphs } scope subscribes to several docs.
|
||||
const ngSet = useShape(shapeType, storeNuri as any) as DeepSignalSet<NgT>;
|
||||
// `useShapeSet` is what unwraps the ORM's result object — see that module.
|
||||
const ngSet = useShapeSet(shapeType, storeNuri);
|
||||
const usingDefaults = !shapesReady;
|
||||
const items = usingDefaults ? defaults : [...ngSet].map(item => mapFromNg(item as unknown as NgT));
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ import { createRoot } from 'react-dom/client';
|
||||
import { NextGraphProvider, useNextGraph } from '../context/NextGraphContext';
|
||||
import { RefineProvider } from '../refine/RefineProvider';
|
||||
import { FestipodDataProvider, useFestipodData } from '../context/FestipodDataContext';
|
||||
import { useShape } from '@ng-org/orm/react';
|
||||
import { useShapeSet } from '../hooks/useShapeSet';
|
||||
import { ng } from '@ng-org/web';
|
||||
import type { DeepSignalSet } from '@ng-org/alien-deepsignals';
|
||||
import {
|
||||
@@ -67,9 +67,9 @@ function ConnectedHarness() {
|
||||
|
||||
// Use private store NURI as scope (opens the repo for reads AND writes)
|
||||
const privateNuri = ngCtx.session && `did:ng:${ngCtx.session.private_store_id}`;
|
||||
const events = useShape(FpEventShapeType, privateNuri) as DeepSignalSet<FpEvent>;
|
||||
const users = useShape(FpUserProfileShapeType, privateNuri) as DeepSignalSet<FpUserProfile>;
|
||||
const participations = useShape(FpParticipationShapeType, privateNuri) as DeepSignalSet<FpParticipation>;
|
||||
const events = useShapeSet(FpEventShapeType, privateNuri);
|
||||
const users = useShapeSet(FpUserProfileShapeType, privateNuri);
|
||||
const participations = useShapeSet(FpParticipationShapeType, privateNuri);
|
||||
|
||||
const [bridgeReady, setBridgeReady] = useState(false);
|
||||
// Stopgap multi-store validation: a doc created on demand via doc_create,
|
||||
@@ -239,7 +239,7 @@ function ConnectedHarness() {
|
||||
// ============================================================================
|
||||
|
||||
function FanoutProbe({ graphs }: { graphs: string[] }) {
|
||||
const set = useShape(FpEventShapeType, { graphs } as any) as DeepSignalSet<FpEvent>;
|
||||
const set = useShapeSet(FpEventShapeType, { graphs });
|
||||
useEffect(() => {
|
||||
(window as any).__fanout = {
|
||||
ready: true,
|
||||
@@ -266,7 +266,7 @@ function FanoutProbe({ graphs }: { graphs: string[] }) {
|
||||
// ============================================================================
|
||||
|
||||
function SmokeProbe({ docNuri }: { docNuri: string }) {
|
||||
const set = useShape(FpParticipationShapeType, { graphs: [docNuri] } as any) as DeepSignalSet<FpParticipation>;
|
||||
const set = useShapeSet(FpParticipationShapeType, { graphs: [docNuri] });
|
||||
useEffect(() => {
|
||||
(window as any).__smoke = {
|
||||
ready: true,
|
||||
|
||||
Reference in New Issue
Block a user