@ai-matrx/data 0.18.34 → 0.18.35
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/CHANGELOG.md +11 -0
- package/dist/db.cjs +239 -1
- package/dist/db.cjs.map +1 -1
- package/dist/db.d.cts +201 -1
- package/dist/db.d.ts +201 -1
- package/dist/db.js +239 -1
- package/dist/db.js.map +1 -1
- package/package.json +3 -2
package/dist/db.d.cts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { MaybeSingleResponse } from './index.cjs';
|
|
2
2
|
export { a as AuthCookie, b as AuthCookieOptions, A as AuthCookieValue, C as CreateAuthCookieArgs, D as DEFAULT_AUTH_COOKIE_NAME, R as ReconciledAuthCookies, c as createAuthCookie, p as parseCookieHeader } from './auth-cookie-BpgVyFw9.cjs';
|
|
3
3
|
export { uniqueChannelTopic } from '@ai-matrx/realtime';
|
|
4
|
+
import { PostgrestError } from '@supabase/supabase-js';
|
|
4
5
|
import './credentials-B-rePKU7.cjs';
|
|
5
6
|
import './net.cjs';
|
|
6
7
|
|
|
@@ -561,4 +562,203 @@ declare function decodeOAuthRefreshClaims(token: string): JwtClaims | null;
|
|
|
561
562
|
*/
|
|
562
563
|
declare function createOAuthRefreshFetch(args: CreateOAuthRefreshFetchArgs): typeof globalThis.fetch;
|
|
563
564
|
|
|
564
|
-
|
|
565
|
+
/** A non-2xx answer from a server, with the words already chosen. */
|
|
566
|
+
declare class WriteRefusedError extends Error {
|
|
567
|
+
readonly status: number;
|
|
568
|
+
/** What the server said for a person, when it said something a person can read. */
|
|
569
|
+
readonly serverMessage: string | null;
|
|
570
|
+
/** The developer line (method, path, status, body) — for diagnostics, never for the screen. */
|
|
571
|
+
readonly technical: string;
|
|
572
|
+
constructor(args: {
|
|
573
|
+
status: number;
|
|
574
|
+
serverMessage?: string | null;
|
|
575
|
+
technical: string;
|
|
576
|
+
});
|
|
577
|
+
}
|
|
578
|
+
/** Pull a human sentence out of a FastAPI-style error body, or null when there is none. */
|
|
579
|
+
declare function serverMessageFromBody(body: unknown): string | null;
|
|
580
|
+
/**
|
|
581
|
+
* THE STORE'S OWN WORDS, WHEN IT SAID SOME (lane HANDOVER, 2026-09-27). A door that refuses raises a
|
|
582
|
+
* sentence for a person — `You can see this record, but "Allergies" is not yours to change.` — or the
|
|
583
|
+
* same sentence behind its own name (`cat_write: this category is not yours to change.`). Postgres's
|
|
584
|
+
* native refusals (`permission denied for table …`, `new row violates …`) are never that. This hands
|
|
585
|
+
* back the door's sentence, capitalised and ended, or null when the text is not a person's words.
|
|
586
|
+
* test@test.com's refused Allergies edit read "Permission denied" because the scopes mapper threw
|
|
587
|
+
* the door's sentence away for a fixed string.
|
|
588
|
+
*/
|
|
589
|
+
declare function personSentence(text: unknown): string | null;
|
|
590
|
+
/**
|
|
591
|
+
* A direct Supabase write that matched no row answers WITHOUT an error (RLS filters the row out,
|
|
592
|
+
* or it is gone), so the screen would say "saved" over nothing. Pass the `.select()` rows of an
|
|
593
|
+
* update/upsert: zero rows is a refusal, said in words.
|
|
594
|
+
*/
|
|
595
|
+
declare function assertWriteLanded<T>(rows: T[] | T | null | undefined, technical: string): void;
|
|
596
|
+
interface WriteFailureWords {
|
|
597
|
+
/** "Could not pause this schedule." */
|
|
598
|
+
title: string;
|
|
599
|
+
/** Why, in words, then the remedy. */
|
|
600
|
+
description: string;
|
|
601
|
+
}
|
|
602
|
+
/**
|
|
603
|
+
* The words for a failed write. `action` completes "Could not …" ("pause this schedule");
|
|
604
|
+
* `remedy` is what to do next ("Try again or open the schedule's activity.").
|
|
605
|
+
*/
|
|
606
|
+
declare function describeWriteFailure(err: unknown, { action, remedy }: {
|
|
607
|
+
action: string;
|
|
608
|
+
remedy?: string;
|
|
609
|
+
}): WriteFailureWords;
|
|
610
|
+
|
|
611
|
+
/**
|
|
612
|
+
* @ai-matrx/data/db — writeOne: THE single-record write that PROVES it landed.
|
|
613
|
+
*
|
|
614
|
+
* Ported verbatim from matrx-frontend `utils/supabase/writeOne.ts` (which now
|
|
615
|
+
* re-exports this module, so `WriteDidNotLandError` is ONE class everywhere).
|
|
616
|
+
*
|
|
617
|
+
* THE CLASS (flashcards, 2026-09-25). PostgREST answers an `.update()` or
|
|
618
|
+
* `.delete()` that row-level security filters to zero rows with
|
|
619
|
+
* `{ data: [], error: null }` — status 200, no error. A caller that judges
|
|
620
|
+
* success by `error == null` therefore tells the person "Card deleted" while
|
|
621
|
+
* nothing happened. The same is true when the row is already gone. Nothing
|
|
622
|
+
* fails silently: zero rows written is a refusal, and it is said in words.
|
|
623
|
+
*
|
|
624
|
+
* USE IT for every write aimed at ONE record (`.eq("id", …)`):
|
|
625
|
+
*
|
|
626
|
+
* await writeOne(
|
|
627
|
+
* supabase.from("notes").update({ label }).eq("id", noteId).select("id"),
|
|
628
|
+
* { action: "rename", noun: "note" },
|
|
629
|
+
* );
|
|
630
|
+
*
|
|
631
|
+
* - The builder MUST end in `.select(...)` — that is what makes PostgREST
|
|
632
|
+
* return the rows it actually wrote. Select only what you need (`"id"` is
|
|
633
|
+
* fine); the first written row is returned.
|
|
634
|
+
* - A database error is thrown AS IS (the PostgrestError), so existing error
|
|
635
|
+
* handling and `describeWriteFailure` keep working.
|
|
636
|
+
* - Zero rows throws `WriteDidNotLandError` — a `WriteRefusedError` whose
|
|
637
|
+
* message is already a sentence for a person:
|
|
638
|
+
* "Nothing was deleted: this card no longer exists, or your access does not
|
|
639
|
+
* allow deleting it."
|
|
640
|
+
* - `tryWriteOne` is the same check for result-style services that never
|
|
641
|
+
* throw: it returns `{ row, error }`.
|
|
642
|
+
*
|
|
643
|
+
* TWO MODES for writes that carry an extra condition (coordinator ruling,
|
|
644
|
+
* 2026-09-25):
|
|
645
|
+
*
|
|
646
|
+
* - IDEMPOTENT (`alreadyDone`): an archive filtered `.is("deleted_at", null)`
|
|
647
|
+
* writes zero rows when the record is ALREADY archived — and also when RLS
|
|
648
|
+
* refused. On zero rows the primitive re-reads the row by id: visible and
|
|
649
|
+
* already in the target state → success (the re-read row is returned);
|
|
650
|
+
* invisible or still live → the plain refusal.
|
|
651
|
+
*
|
|
652
|
+
* - COMPARE-AND-SET (`compareAndSet: true`): `.eq("status", "pending")`,
|
|
653
|
+
* `.is("answered_at", null)`… zero rows means someone else got there first.
|
|
654
|
+
* That is still told to the person: "This answer was already handled —
|
|
655
|
+
* refresh to see the latest." (`WriteDidNotLandError.reason === "taken"`).
|
|
656
|
+
*
|
|
657
|
+
* Optimistic-concurrency edits with a `version` token use `guardedUpdate`
|
|
658
|
+
* from `@ai-matrx/data/db`, which classifies zero rows as conflict or gone.
|
|
659
|
+
* A bulk `.in(...)` write is not a single-record write.
|
|
660
|
+
*
|
|
661
|
+
* Guard: `pnpm check:single-record-writes` (scripts/check-single-record-writes.ts)
|
|
662
|
+
* fails on a new single-record update/delete that judges success by error alone.
|
|
663
|
+
*/
|
|
664
|
+
|
|
665
|
+
/** The verb the person asked for. Drives the refusal sentence. */
|
|
666
|
+
type WriteOneAction = "save" | "update" | "rename" | "move" | "delete" | "remove" | "archive" | "restore" | "publish" | "unpublish" | "change";
|
|
667
|
+
interface WriteOneOptions<Row = unknown> {
|
|
668
|
+
/** What the person asked for: "delete", "save", "archive"… */
|
|
669
|
+
action: WriteOneAction;
|
|
670
|
+
/** What it is, in the person's words: "card", "note", "saved view". */
|
|
671
|
+
noun: string;
|
|
672
|
+
/**
|
|
673
|
+
* Idempotent mode. On zero rows, `reread()` the record by id (end it in
|
|
674
|
+
* `.maybeSingle()`); when it is visible and `isDone(row)`, the write is a
|
|
675
|
+
* success and that row is returned. Otherwise the plain refusal.
|
|
676
|
+
*/
|
|
677
|
+
alreadyDone?: {
|
|
678
|
+
reread: () => PromiseLike<{
|
|
679
|
+
data: Row | null;
|
|
680
|
+
error: unknown;
|
|
681
|
+
}>;
|
|
682
|
+
isDone: (row: Row) => boolean;
|
|
683
|
+
};
|
|
684
|
+
/** Compare-and-set mode: zero rows means someone else got there first. */
|
|
685
|
+
compareAndSet?: boolean;
|
|
686
|
+
}
|
|
687
|
+
/** Why nothing was written: refused/gone, or another writer took it first. */
|
|
688
|
+
type WriteDidNotLandReason = "refused" | "taken";
|
|
689
|
+
/** Zero rows were written: the record is gone, or the person may not change it. */
|
|
690
|
+
declare class WriteDidNotLandError extends WriteRefusedError {
|
|
691
|
+
readonly action: WriteOneAction;
|
|
692
|
+
readonly noun: string;
|
|
693
|
+
readonly reason: WriteDidNotLandReason;
|
|
694
|
+
constructor({ action, noun }: Pick<WriteOneOptions, "action" | "noun">, reason?: WriteDidNotLandReason);
|
|
695
|
+
}
|
|
696
|
+
/** Structural shape of an awaited supabase-js builder that ended in `.select()`. */
|
|
697
|
+
interface RowsResponse<Row> {
|
|
698
|
+
data: Row[] | null;
|
|
699
|
+
error: unknown;
|
|
700
|
+
}
|
|
701
|
+
/**
|
|
702
|
+
* Run a single-record write and prove it landed. Returns the first written
|
|
703
|
+
* row; throws the database error as is, or `WriteDidNotLandError` on zero rows.
|
|
704
|
+
*/
|
|
705
|
+
declare function writeOne<Row>(write: PromiseLike<RowsResponse<Row>>, options: WriteOneOptions<Row>): Promise<Row>;
|
|
706
|
+
type TryWriteOneResult<Row> = {
|
|
707
|
+
row: Row;
|
|
708
|
+
error: null;
|
|
709
|
+
} | {
|
|
710
|
+
row: null;
|
|
711
|
+
error: Error;
|
|
712
|
+
};
|
|
713
|
+
/** `writeOne` for services that return results instead of throwing. */
|
|
714
|
+
declare function tryWriteOne<Row>(write: PromiseLike<RowsResponse<Row>>, options: WriteOneOptions<Row>): Promise<TryWriteOneResult<Row>>;
|
|
715
|
+
/**
|
|
716
|
+
* The HTTP status a route handler answers with for a failed write: the
|
|
717
|
+
* refusal's own status (403 refused / 409 taken) for `WriteDidNotLandError`,
|
|
718
|
+
* Database authorization/not-found/conflict codes retain their HTTP meaning;
|
|
719
|
+
* unknown failures remain 500. Server twin of the client's toast path.
|
|
720
|
+
*/
|
|
721
|
+
declare function writeFailureStatus(error: unknown): number;
|
|
722
|
+
/**
|
|
723
|
+
* Several single-record writes where some did not land. The message names how
|
|
724
|
+
* many of how many failed and why (the first reason); `failures` keeps each
|
|
725
|
+
* record id with its own sentence so a screen can mark exactly those rows.
|
|
726
|
+
*/
|
|
727
|
+
declare class BulkWriteError extends Error {
|
|
728
|
+
readonly failures: {
|
|
729
|
+
id: string;
|
|
730
|
+
message: string;
|
|
731
|
+
}[];
|
|
732
|
+
readonly attempted: number;
|
|
733
|
+
constructor(failures: {
|
|
734
|
+
id: string;
|
|
735
|
+
message: string;
|
|
736
|
+
}[], attempted: number);
|
|
737
|
+
}
|
|
738
|
+
type WriteOneRowResult<Row> = {
|
|
739
|
+
data: Row;
|
|
740
|
+
error: null;
|
|
741
|
+
} | {
|
|
742
|
+
data: null;
|
|
743
|
+
error: PostgrestError;
|
|
744
|
+
};
|
|
745
|
+
/**
|
|
746
|
+
* The drop-in for `.update(…).eq("id", …).select(…).single()` (and the
|
|
747
|
+
* `.maybeSingle()` twin whose caller never looks at `data`): same
|
|
748
|
+
* `{ data, error }` shape, same `PostgrestError` type, same `PGRST116` code on
|
|
749
|
+
* zero rows — but the message is the person's sentence ("Nothing was updated:
|
|
750
|
+
* this deal no longer exists, or your access does not allow updating it.")
|
|
751
|
+
* instead of PostgREST's "JSON object requested, multiple (or no) rows
|
|
752
|
+
* returned", which every screen rendered as a vague failure.
|
|
753
|
+
*
|
|
754
|
+
* const { data, error } = await writeOneRow(
|
|
755
|
+
* db.from("deal").update(patch).eq("id", id).select("*"),
|
|
756
|
+
* { action: "update", noun: "deal" },
|
|
757
|
+
* );
|
|
758
|
+
*
|
|
759
|
+
* Guard: `pnpm check:single-record-writes` flags the bare `.select().single()`
|
|
760
|
+
* shape on a write.
|
|
761
|
+
*/
|
|
762
|
+
declare function writeOneRow<Row>(write: PromiseLike<RowsResponse<Row>>, options: Pick<WriteOneOptions, "action" | "noun" | "compareAndSet">): Promise<WriteOneRowResult<Row>>;
|
|
763
|
+
|
|
764
|
+
export { type ActiveOrgCookie, type ActiveOrgCookieOptions, type ActiveOrgSelection, type AuthRetryableResult, BulkWriteError, type CreateActiveOrgCookieArgs, type CreateOAuthRefreshFetchArgs, type CreateSessionRetryOptions, DEFAULT_ACTIVE_ORG_COOKIE_MAX_AGE, DEFAULT_ACTIVE_ORG_COOKIE_NAME, type GuardedUpdateArgs, type GuardedUpdateResult, IncompleteReadError, type JsonObject, type MergeJsonColumnArgs, type MergeJsonColumnResult, type OAuthRefreshStorage, type PagedReadQuery, type PagedReadResult, type ReadAllRowsOptions, type ReadAllRowsRestOptions, type SessionSource, SessionUnavailableError, type TryWriteOneResult, type VersionedRow, WriteDidNotLandError, type WriteDidNotLandReason, type WriteFailureWords, type WriteOneAction, type WriteOneOptions, type WriteOneRowResult, WriteRefusedError, asJsonObject, assertWriteLanded, createActiveOrgCookie, createOAuthRefreshFetch, createSessionRetry, decodeActiveOrgSelection, decodeOAuthRefreshClaims, describeWriteFailure, encodeActiveOrgSelection, guardedUpdate, isMissingSessionError, mergeJsonColumn, personSentence, readAllRows, readAllRowsRest, serverMessageFromBody, tryReadAllRows, tryReadAllRowsRest, tryWriteOne, writeFailureStatus, writeOne, writeOneRow };
|
package/dist/db.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { MaybeSingleResponse } from './index.js';
|
|
2
2
|
export { a as AuthCookie, b as AuthCookieOptions, A as AuthCookieValue, C as CreateAuthCookieArgs, D as DEFAULT_AUTH_COOKIE_NAME, R as ReconciledAuthCookies, c as createAuthCookie, p as parseCookieHeader } from './auth-cookie-BpgVyFw9.js';
|
|
3
3
|
export { uniqueChannelTopic } from '@ai-matrx/realtime';
|
|
4
|
+
import { PostgrestError } from '@supabase/supabase-js';
|
|
4
5
|
import './credentials-B-rePKU7.js';
|
|
5
6
|
import './net.js';
|
|
6
7
|
|
|
@@ -561,4 +562,203 @@ declare function decodeOAuthRefreshClaims(token: string): JwtClaims | null;
|
|
|
561
562
|
*/
|
|
562
563
|
declare function createOAuthRefreshFetch(args: CreateOAuthRefreshFetchArgs): typeof globalThis.fetch;
|
|
563
564
|
|
|
564
|
-
|
|
565
|
+
/** A non-2xx answer from a server, with the words already chosen. */
|
|
566
|
+
declare class WriteRefusedError extends Error {
|
|
567
|
+
readonly status: number;
|
|
568
|
+
/** What the server said for a person, when it said something a person can read. */
|
|
569
|
+
readonly serverMessage: string | null;
|
|
570
|
+
/** The developer line (method, path, status, body) — for diagnostics, never for the screen. */
|
|
571
|
+
readonly technical: string;
|
|
572
|
+
constructor(args: {
|
|
573
|
+
status: number;
|
|
574
|
+
serverMessage?: string | null;
|
|
575
|
+
technical: string;
|
|
576
|
+
});
|
|
577
|
+
}
|
|
578
|
+
/** Pull a human sentence out of a FastAPI-style error body, or null when there is none. */
|
|
579
|
+
declare function serverMessageFromBody(body: unknown): string | null;
|
|
580
|
+
/**
|
|
581
|
+
* THE STORE'S OWN WORDS, WHEN IT SAID SOME (lane HANDOVER, 2026-09-27). A door that refuses raises a
|
|
582
|
+
* sentence for a person — `You can see this record, but "Allergies" is not yours to change.` — or the
|
|
583
|
+
* same sentence behind its own name (`cat_write: this category is not yours to change.`). Postgres's
|
|
584
|
+
* native refusals (`permission denied for table …`, `new row violates …`) are never that. This hands
|
|
585
|
+
* back the door's sentence, capitalised and ended, or null when the text is not a person's words.
|
|
586
|
+
* test@test.com's refused Allergies edit read "Permission denied" because the scopes mapper threw
|
|
587
|
+
* the door's sentence away for a fixed string.
|
|
588
|
+
*/
|
|
589
|
+
declare function personSentence(text: unknown): string | null;
|
|
590
|
+
/**
|
|
591
|
+
* A direct Supabase write that matched no row answers WITHOUT an error (RLS filters the row out,
|
|
592
|
+
* or it is gone), so the screen would say "saved" over nothing. Pass the `.select()` rows of an
|
|
593
|
+
* update/upsert: zero rows is a refusal, said in words.
|
|
594
|
+
*/
|
|
595
|
+
declare function assertWriteLanded<T>(rows: T[] | T | null | undefined, technical: string): void;
|
|
596
|
+
interface WriteFailureWords {
|
|
597
|
+
/** "Could not pause this schedule." */
|
|
598
|
+
title: string;
|
|
599
|
+
/** Why, in words, then the remedy. */
|
|
600
|
+
description: string;
|
|
601
|
+
}
|
|
602
|
+
/**
|
|
603
|
+
* The words for a failed write. `action` completes "Could not …" ("pause this schedule");
|
|
604
|
+
* `remedy` is what to do next ("Try again or open the schedule's activity.").
|
|
605
|
+
*/
|
|
606
|
+
declare function describeWriteFailure(err: unknown, { action, remedy }: {
|
|
607
|
+
action: string;
|
|
608
|
+
remedy?: string;
|
|
609
|
+
}): WriteFailureWords;
|
|
610
|
+
|
|
611
|
+
/**
|
|
612
|
+
* @ai-matrx/data/db — writeOne: THE single-record write that PROVES it landed.
|
|
613
|
+
*
|
|
614
|
+
* Ported verbatim from matrx-frontend `utils/supabase/writeOne.ts` (which now
|
|
615
|
+
* re-exports this module, so `WriteDidNotLandError` is ONE class everywhere).
|
|
616
|
+
*
|
|
617
|
+
* THE CLASS (flashcards, 2026-09-25). PostgREST answers an `.update()` or
|
|
618
|
+
* `.delete()` that row-level security filters to zero rows with
|
|
619
|
+
* `{ data: [], error: null }` — status 200, no error. A caller that judges
|
|
620
|
+
* success by `error == null` therefore tells the person "Card deleted" while
|
|
621
|
+
* nothing happened. The same is true when the row is already gone. Nothing
|
|
622
|
+
* fails silently: zero rows written is a refusal, and it is said in words.
|
|
623
|
+
*
|
|
624
|
+
* USE IT for every write aimed at ONE record (`.eq("id", …)`):
|
|
625
|
+
*
|
|
626
|
+
* await writeOne(
|
|
627
|
+
* supabase.from("notes").update({ label }).eq("id", noteId).select("id"),
|
|
628
|
+
* { action: "rename", noun: "note" },
|
|
629
|
+
* );
|
|
630
|
+
*
|
|
631
|
+
* - The builder MUST end in `.select(...)` — that is what makes PostgREST
|
|
632
|
+
* return the rows it actually wrote. Select only what you need (`"id"` is
|
|
633
|
+
* fine); the first written row is returned.
|
|
634
|
+
* - A database error is thrown AS IS (the PostgrestError), so existing error
|
|
635
|
+
* handling and `describeWriteFailure` keep working.
|
|
636
|
+
* - Zero rows throws `WriteDidNotLandError` — a `WriteRefusedError` whose
|
|
637
|
+
* message is already a sentence for a person:
|
|
638
|
+
* "Nothing was deleted: this card no longer exists, or your access does not
|
|
639
|
+
* allow deleting it."
|
|
640
|
+
* - `tryWriteOne` is the same check for result-style services that never
|
|
641
|
+
* throw: it returns `{ row, error }`.
|
|
642
|
+
*
|
|
643
|
+
* TWO MODES for writes that carry an extra condition (coordinator ruling,
|
|
644
|
+
* 2026-09-25):
|
|
645
|
+
*
|
|
646
|
+
* - IDEMPOTENT (`alreadyDone`): an archive filtered `.is("deleted_at", null)`
|
|
647
|
+
* writes zero rows when the record is ALREADY archived — and also when RLS
|
|
648
|
+
* refused. On zero rows the primitive re-reads the row by id: visible and
|
|
649
|
+
* already in the target state → success (the re-read row is returned);
|
|
650
|
+
* invisible or still live → the plain refusal.
|
|
651
|
+
*
|
|
652
|
+
* - COMPARE-AND-SET (`compareAndSet: true`): `.eq("status", "pending")`,
|
|
653
|
+
* `.is("answered_at", null)`… zero rows means someone else got there first.
|
|
654
|
+
* That is still told to the person: "This answer was already handled —
|
|
655
|
+
* refresh to see the latest." (`WriteDidNotLandError.reason === "taken"`).
|
|
656
|
+
*
|
|
657
|
+
* Optimistic-concurrency edits with a `version` token use `guardedUpdate`
|
|
658
|
+
* from `@ai-matrx/data/db`, which classifies zero rows as conflict or gone.
|
|
659
|
+
* A bulk `.in(...)` write is not a single-record write.
|
|
660
|
+
*
|
|
661
|
+
* Guard: `pnpm check:single-record-writes` (scripts/check-single-record-writes.ts)
|
|
662
|
+
* fails on a new single-record update/delete that judges success by error alone.
|
|
663
|
+
*/
|
|
664
|
+
|
|
665
|
+
/** The verb the person asked for. Drives the refusal sentence. */
|
|
666
|
+
type WriteOneAction = "save" | "update" | "rename" | "move" | "delete" | "remove" | "archive" | "restore" | "publish" | "unpublish" | "change";
|
|
667
|
+
interface WriteOneOptions<Row = unknown> {
|
|
668
|
+
/** What the person asked for: "delete", "save", "archive"… */
|
|
669
|
+
action: WriteOneAction;
|
|
670
|
+
/** What it is, in the person's words: "card", "note", "saved view". */
|
|
671
|
+
noun: string;
|
|
672
|
+
/**
|
|
673
|
+
* Idempotent mode. On zero rows, `reread()` the record by id (end it in
|
|
674
|
+
* `.maybeSingle()`); when it is visible and `isDone(row)`, the write is a
|
|
675
|
+
* success and that row is returned. Otherwise the plain refusal.
|
|
676
|
+
*/
|
|
677
|
+
alreadyDone?: {
|
|
678
|
+
reread: () => PromiseLike<{
|
|
679
|
+
data: Row | null;
|
|
680
|
+
error: unknown;
|
|
681
|
+
}>;
|
|
682
|
+
isDone: (row: Row) => boolean;
|
|
683
|
+
};
|
|
684
|
+
/** Compare-and-set mode: zero rows means someone else got there first. */
|
|
685
|
+
compareAndSet?: boolean;
|
|
686
|
+
}
|
|
687
|
+
/** Why nothing was written: refused/gone, or another writer took it first. */
|
|
688
|
+
type WriteDidNotLandReason = "refused" | "taken";
|
|
689
|
+
/** Zero rows were written: the record is gone, or the person may not change it. */
|
|
690
|
+
declare class WriteDidNotLandError extends WriteRefusedError {
|
|
691
|
+
readonly action: WriteOneAction;
|
|
692
|
+
readonly noun: string;
|
|
693
|
+
readonly reason: WriteDidNotLandReason;
|
|
694
|
+
constructor({ action, noun }: Pick<WriteOneOptions, "action" | "noun">, reason?: WriteDidNotLandReason);
|
|
695
|
+
}
|
|
696
|
+
/** Structural shape of an awaited supabase-js builder that ended in `.select()`. */
|
|
697
|
+
interface RowsResponse<Row> {
|
|
698
|
+
data: Row[] | null;
|
|
699
|
+
error: unknown;
|
|
700
|
+
}
|
|
701
|
+
/**
|
|
702
|
+
* Run a single-record write and prove it landed. Returns the first written
|
|
703
|
+
* row; throws the database error as is, or `WriteDidNotLandError` on zero rows.
|
|
704
|
+
*/
|
|
705
|
+
declare function writeOne<Row>(write: PromiseLike<RowsResponse<Row>>, options: WriteOneOptions<Row>): Promise<Row>;
|
|
706
|
+
type TryWriteOneResult<Row> = {
|
|
707
|
+
row: Row;
|
|
708
|
+
error: null;
|
|
709
|
+
} | {
|
|
710
|
+
row: null;
|
|
711
|
+
error: Error;
|
|
712
|
+
};
|
|
713
|
+
/** `writeOne` for services that return results instead of throwing. */
|
|
714
|
+
declare function tryWriteOne<Row>(write: PromiseLike<RowsResponse<Row>>, options: WriteOneOptions<Row>): Promise<TryWriteOneResult<Row>>;
|
|
715
|
+
/**
|
|
716
|
+
* The HTTP status a route handler answers with for a failed write: the
|
|
717
|
+
* refusal's own status (403 refused / 409 taken) for `WriteDidNotLandError`,
|
|
718
|
+
* Database authorization/not-found/conflict codes retain their HTTP meaning;
|
|
719
|
+
* unknown failures remain 500. Server twin of the client's toast path.
|
|
720
|
+
*/
|
|
721
|
+
declare function writeFailureStatus(error: unknown): number;
|
|
722
|
+
/**
|
|
723
|
+
* Several single-record writes where some did not land. The message names how
|
|
724
|
+
* many of how many failed and why (the first reason); `failures` keeps each
|
|
725
|
+
* record id with its own sentence so a screen can mark exactly those rows.
|
|
726
|
+
*/
|
|
727
|
+
declare class BulkWriteError extends Error {
|
|
728
|
+
readonly failures: {
|
|
729
|
+
id: string;
|
|
730
|
+
message: string;
|
|
731
|
+
}[];
|
|
732
|
+
readonly attempted: number;
|
|
733
|
+
constructor(failures: {
|
|
734
|
+
id: string;
|
|
735
|
+
message: string;
|
|
736
|
+
}[], attempted: number);
|
|
737
|
+
}
|
|
738
|
+
type WriteOneRowResult<Row> = {
|
|
739
|
+
data: Row;
|
|
740
|
+
error: null;
|
|
741
|
+
} | {
|
|
742
|
+
data: null;
|
|
743
|
+
error: PostgrestError;
|
|
744
|
+
};
|
|
745
|
+
/**
|
|
746
|
+
* The drop-in for `.update(…).eq("id", …).select(…).single()` (and the
|
|
747
|
+
* `.maybeSingle()` twin whose caller never looks at `data`): same
|
|
748
|
+
* `{ data, error }` shape, same `PostgrestError` type, same `PGRST116` code on
|
|
749
|
+
* zero rows — but the message is the person's sentence ("Nothing was updated:
|
|
750
|
+
* this deal no longer exists, or your access does not allow updating it.")
|
|
751
|
+
* instead of PostgREST's "JSON object requested, multiple (or no) rows
|
|
752
|
+
* returned", which every screen rendered as a vague failure.
|
|
753
|
+
*
|
|
754
|
+
* const { data, error } = await writeOneRow(
|
|
755
|
+
* db.from("deal").update(patch).eq("id", id).select("*"),
|
|
756
|
+
* { action: "update", noun: "deal" },
|
|
757
|
+
* );
|
|
758
|
+
*
|
|
759
|
+
* Guard: `pnpm check:single-record-writes` flags the bare `.select().single()`
|
|
760
|
+
* shape on a write.
|
|
761
|
+
*/
|
|
762
|
+
declare function writeOneRow<Row>(write: PromiseLike<RowsResponse<Row>>, options: Pick<WriteOneOptions, "action" | "noun" | "compareAndSet">): Promise<WriteOneRowResult<Row>>;
|
|
763
|
+
|
|
764
|
+
export { type ActiveOrgCookie, type ActiveOrgCookieOptions, type ActiveOrgSelection, type AuthRetryableResult, BulkWriteError, type CreateActiveOrgCookieArgs, type CreateOAuthRefreshFetchArgs, type CreateSessionRetryOptions, DEFAULT_ACTIVE_ORG_COOKIE_MAX_AGE, DEFAULT_ACTIVE_ORG_COOKIE_NAME, type GuardedUpdateArgs, type GuardedUpdateResult, IncompleteReadError, type JsonObject, type MergeJsonColumnArgs, type MergeJsonColumnResult, type OAuthRefreshStorage, type PagedReadQuery, type PagedReadResult, type ReadAllRowsOptions, type ReadAllRowsRestOptions, type SessionSource, SessionUnavailableError, type TryWriteOneResult, type VersionedRow, WriteDidNotLandError, type WriteDidNotLandReason, type WriteFailureWords, type WriteOneAction, type WriteOneOptions, type WriteOneRowResult, WriteRefusedError, asJsonObject, assertWriteLanded, createActiveOrgCookie, createOAuthRefreshFetch, createSessionRetry, decodeActiveOrgSelection, decodeOAuthRefreshClaims, describeWriteFailure, encodeActiveOrgSelection, guardedUpdate, isMissingSessionError, mergeJsonColumn, personSentence, readAllRows, readAllRowsRest, serverMessageFromBody, tryReadAllRows, tryReadAllRowsRest, tryWriteOne, writeFailureStatus, writeOne, writeOneRow };
|