@mica/sdk (SDK contract v1)
    Preparing search index...

    Function createCrudStore

    The list-store factory, so an add-on can have one.

    It lives in services/ because core's own services use it, and it is exported here because an app cannot import by path (§2.7) — without this line, building a list means reimplementing the ordering, the loaded flag and the no-optimism rule that took several rewrites to settle. Paired with CrudOptions.service, which is how an app reaches its own server without a route table entry.

    • A list over an add-on's own service, typed from its declaration (MICA-313).

      The declaration your server half registers, and the actions to use from it. Every name is one of the declaration's, and the store's types follow from the declaration rather than from a type argument you assert:

      • list answers addonOutput<Row[]>(), and Row — which needs an id: number — is the store's row type. It is called with no input.
      • create takes the draft, so add(draft) is typed as its input, and answers the created row (addonOutput<Row>()).
      • update is sent the whole row, so its input declares every field of Row.
      • remove is sent { id }.
      const notes = createCrudStore(notesService, { list: 'list', create: 'add' });
      await notes.add({ text }); // a typo in a field, or in an action name, fails to compile

      At run time it is createCrudStore(declaration.id, events, { ...options, service: declaration.id }): the same store, through the same generic service route. The types are a convenience and a promise about nothing — the server parses every payload against the declaration it was handed, and the shell refuses any id but your own, whatever this side believed.

      Type Parameters

      • const D extends AddonServiceDeclaration
      • L extends string
      • C extends string = never
      • U extends string = never
      • R extends string = never

      Parameters

      • declaration: D
      • events: {
            create?: CreateEvent<D, C, ListedRow<D, L>>;
            list: ListEvent<D, L>;
            remove?: RemoveEvent<D, R>;
            update?: UpdateEvent<D, U, ListedRow<D, L>>;
        }
      • Optionaloptions: Omit<CrudOptions<ListedRow<D, L>, DraftOf<D, C>>, "service">

      Returns {
          add: (draft: DraftOf) => Promise<ListedRow<D, L>>;
          delete: (id: number) => Promise<void>;
          load: () => Promise<void>;
          loaded: {
              subscribe: (
                  this: void,
                  run: Subscriber<boolean>,
                  invalidate?: () => void,
              ) => Unsubscriber;
          };
          patch: (id: number, changes: Partial<T>) => void;
          set: (rows: ListedRow<D, L>[]) => void;
          subscribe: (
              this: void,
              run: Subscriber<ListedRow<D, L>[]>,
              invalidate?: () => void,
          ) => Unsubscriber;
          update: (row: ListedRow) => Promise<void>;
      }

      • add: (draft: DraftOf) => Promise<ListedRow<D, L>>
      • delete: (id: number) => Promise<void>
      • load: () => Promise<void>
      • loaded: {
            subscribe: (
                this: void,
                run: Subscriber<boolean>,
                invalidate?: () => void,
            ) => Unsubscriber;
        }
        • subscribe: (this: void, run: Subscriber<boolean>, invalidate?: () => void) => Unsubscriber
      • patch: (id: number, changes: Partial<T>) => void
      • set: (rows: ListedRow<D, L>[]) => void

        For the paths a list alone cannot express — an incoming message, a local patch.

      • subscribe: (
            this: void,
            run: Subscriber<ListedRow<D, L>[]>,
            invalidate?: () => void,
        ) => Unsubscriber
      • update: (row: ListedRow) => Promise<void>
    • A store over a list of rows the server owns.

      Four stores had written the same load/add/update/delete by hand, and every difference between them was an accident rather than a decision:

      • Order — see sort above.
      • Optimism — Mail wrote to the list first and then told the server; everyone else waited. Optimism was load-bearing when fetchNui swallowed failures, because there was no other way to feel responsive. It now throws, so the list follows the server and a refused write no longer leaves the UI asserting something untrue.
      • Validation — Contacts checked its required fields, in the store and again in the component; nobody else checked anything.
      • Bad data — three stores logged and emptied the list on a non-array reply, one let it through. Now a non-array reply is a failed load (MICA-384): logged, and the list and loaded are left as they were.

      Neither reads nor writes pass a defaultValue, so fetchNui throws on failure instead of masking it. Writes propagate the throw to the caller, which already had a value to fall back to. load catches it itself and keeps the store's last known list — the store has nothing better to show, and a background refresh that failed should not wipe out what the player was already looking at. Nor does it set loaded: a first load that failed leaves the store still loading, and calling load again is the retry.

      Type Parameters

      • T extends { id: number }
      • TDraft = Omit<T, "id">

      Parameters

      Returns {
          add: (draft: TDraft) => Promise<T>;
          delete: (id: number) => Promise<void>;
          load: () => Promise<void>;
          loaded: {
              subscribe: (
                  this: void,
                  run: Subscriber<boolean>,
                  invalidate?: () => void,
              ) => Unsubscriber;
          };
          patch: (id: number, changes: Partial<T>) => void;
          set: (rows: T[]) => void;
          subscribe: (
              this: void,
              run: Subscriber<T[]>,
              invalidate?: () => void,
          ) => Unsubscriber;
          update: (row: T) => Promise<void>;
      }

      • add: (draft: TDraft) => Promise<T>
      • delete: (id: number) => Promise<void>
      • load: () => Promise<void>
      • loaded: {
            subscribe: (
                this: void,
                run: Subscriber<boolean>,
                invalidate?: () => void,
            ) => Unsubscriber;
        }
        • subscribe: (this: void, run: Subscriber<boolean>, invalidate?: () => void) => Unsubscriber
      • patch: (id: number, changes: Partial<T>) => void
      • set: (rows: T[]) => void

        For the paths a list alone cannot express — an incoming message, a local patch.

      • subscribe: (this: void, run: Subscriber<T[]>, invalidate?: () => void) => Unsubscriber
      • update: (row: T) => Promise<void>