@ai-matrx/data 0.18.34 → 0.18.36

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 CHANGED
@@ -1,5 +1,24 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.18.36
4
+
5
+ Automatic changed-only republish (docs/metadata drift since the last tag — see
6
+ `git diff npm/data/v0.18.35..npm/data/v0.18.36 -- apps/shared/data`).
7
+ No source changes intended and no consumer action required.
8
+
9
+ ## 0.18.35
10
+ ### Unreleased
11
+
12
+ - `/db` gains `writeOne`, `tryWriteOne`, `writeOneRow`, `writeFailureStatus`, `WriteDidNotLandError`,
13
+ `BulkWriteError` and the write-failure words (`WriteRefusedError`, `describeWriteFailure`,
14
+ `assertWriteLanded`, `personSentence`, `serverMessageFromBody`) — moved verbatim from
15
+ matrx-frontend so `@ai-matrx/chat` stops reaching into the app for them.
16
+ - `@supabase/supabase-js` is now a peer dependency (external, never bundled) so `PostgrestError`
17
+ is the consumer's own class and `instanceof` checks hold.
18
+ - `/db` gains `RecordUnavailableError`, `recordUnavailableMessage`, `isRecordUnavailableError`
19
+ (the zero-row single-record read shape), moved from matrx-frontend `lib/records/recordUnavailable.ts`.
20
+ - **Consumer action:** none required; matrx-frontend's old modules re-export these.
21
+
3
22
  ## 0.18.34
4
23
 
5
24
  Automatic changed-only republish (docs/metadata drift since the last tag — see
package/dist/db.cjs CHANGED
@@ -20,28 +20,42 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
20
20
  // src/db.ts
21
21
  var db_exports = {};
22
22
  __export(db_exports, {
23
+ BulkWriteError: () => BulkWriteError,
23
24
  DEFAULT_ACTIVE_ORG_COOKIE_MAX_AGE: () => DEFAULT_ACTIVE_ORG_COOKIE_MAX_AGE,
24
25
  DEFAULT_ACTIVE_ORG_COOKIE_NAME: () => DEFAULT_ACTIVE_ORG_COOKIE_NAME,
25
26
  DEFAULT_AUTH_COOKIE_NAME: () => DEFAULT_AUTH_COOKIE_NAME,
26
27
  IncompleteReadError: () => IncompleteReadError,
28
+ RecordUnavailableError: () => RecordUnavailableError,
27
29
  SessionUnavailableError: () => SessionUnavailableError,
30
+ WriteDidNotLandError: () => WriteDidNotLandError,
31
+ WriteRefusedError: () => WriteRefusedError,
28
32
  asJsonObject: () => asJsonObject,
33
+ assertWriteLanded: () => assertWriteLanded,
29
34
  createActiveOrgCookie: () => createActiveOrgCookie,
30
35
  createAuthCookie: () => createAuthCookie,
31
36
  createOAuthRefreshFetch: () => createOAuthRefreshFetch,
32
37
  createSessionRetry: () => createSessionRetry,
33
38
  decodeActiveOrgSelection: () => decodeActiveOrgSelection,
34
39
  decodeOAuthRefreshClaims: () => decodeOAuthRefreshClaims,
40
+ describeWriteFailure: () => describeWriteFailure,
35
41
  encodeActiveOrgSelection: () => encodeActiveOrgSelection,
36
42
  guardedUpdate: () => guardedUpdate,
37
43
  isMissingSessionError: () => isMissingSessionError,
44
+ isRecordUnavailableError: () => isRecordUnavailableError,
38
45
  mergeJsonColumn: () => mergeJsonColumn,
39
46
  parseCookieHeader: () => parseCookieHeader,
47
+ personSentence: () => personSentence,
40
48
  readAllRows: () => readAllRows,
41
49
  readAllRowsRest: () => readAllRowsRest,
50
+ recordUnavailableMessage: () => recordUnavailableMessage,
51
+ serverMessageFromBody: () => serverMessageFromBody,
42
52
  tryReadAllRows: () => tryReadAllRows,
43
53
  tryReadAllRowsRest: () => tryReadAllRowsRest,
44
- uniqueChannelTopic: () => import_realtime.uniqueChannelTopic
54
+ tryWriteOne: () => tryWriteOne,
55
+ uniqueChannelTopic: () => import_realtime.uniqueChannelTopic,
56
+ writeFailureStatus: () => writeFailureStatus,
57
+ writeOne: () => writeOne,
58
+ writeOneRow: () => writeOneRow
45
59
  });
46
60
  module.exports = __toCommonJS(db_exports);
47
61
 
@@ -675,4 +689,267 @@ function createOAuthRefreshFetch(args) {
675
689
  return normalizeOAuthResponse(adopted, responseSession, clientId);
676
690
  };
677
691
  }
692
+
693
+ // src/db/write-failure.ts
694
+ var WriteRefusedError = class extends Error {
695
+ status;
696
+ /** What the server said for a person, when it said something a person can read. */
697
+ serverMessage;
698
+ /** The developer line (method, path, status, body) — for diagnostics, never for the screen. */
699
+ technical;
700
+ constructor(args) {
701
+ super(refusalSentence(args.status, args.serverMessage ?? null));
702
+ this.name = "WriteRefusedError";
703
+ this.status = args.status;
704
+ this.serverMessage = args.serverMessage ?? null;
705
+ this.technical = args.technical;
706
+ }
707
+ };
708
+ function serverMessageFromBody(body) {
709
+ if (!body || typeof body !== "object") return null;
710
+ const b = body;
711
+ const detail = b.detail;
712
+ const pick = (v) => typeof v === "string" && v.trim() !== "" ? v.trim() : null;
713
+ if (detail && typeof detail === "object" && !Array.isArray(detail)) {
714
+ const d = detail;
715
+ return pick(d.user_message) ?? pick(d.message);
716
+ }
717
+ return pick(b.user_message) ?? pick(detail) ?? pick(b.message);
718
+ }
719
+ var LOOKS_TECHNICAL = /(^|\s)(GET|POST|PUT|PATCH|DELETE)\s+\/|https?:\/\/|\bHTTP \d{3}\b|Traceback|Exception\b|permission denied for|violates|row-level security|\brelation "|\bcolumn "|\bfunction .* does not exist|\bPGRST\d+|\bJWT\b|duplicate key|syntax error|invalid input syntax|JSON object requested|rows returned|\bnull value in column|\bundefined\b|\bat [\w.]+ \(/i;
720
+ function personSentence(text2) {
721
+ if (typeof text2 !== "string") return null;
722
+ let said = text2.trim();
723
+ const named = /^[a-z][a-z0-9_]*[a-z0-9]: (.+)$/s.exec(said);
724
+ if (named) said = (named[1] ?? "").trim().replace(/^[a-z]/, (c) => c.toUpperCase());
725
+ if (said.length < 8 || LOOKS_TECHNICAL.test(said) || !/^[A-Z"“']/.test(said) || !/\s/.test(said)) return null;
726
+ return /[.!?]["”']?$/.test(said) ? said : `${said}.`;
727
+ }
728
+ function isPostgrestShaped(err) {
729
+ if (!err || typeof err !== "object") return false;
730
+ const code = err.code;
731
+ return typeof code === "string" && /^(PGRST\d{3}|[0-9A-Z]{5})$/.test(code) && !("status" in err && typeof err.status === "number");
732
+ }
733
+ function isSerializedRefusal(err) {
734
+ if (!err || typeof err !== "object" || err instanceof Error) return false;
735
+ const e = err;
736
+ if (typeof e.message !== "string" || e.message.trim() === "") return false;
737
+ if (e.name === "WriteRefusedError" || e.name === "WriteDidNotLandError") return true;
738
+ return e.name === "Error" && !LOOKS_TECHNICAL.test(e.message);
739
+ }
740
+ function postgrestSentence(code) {
741
+ if (code === "42501" || code === "PGRST301" || code === "PGRST302") return "You do not have permission to do this.";
742
+ if (code === "PGRST303" || code === "PGRST300") return "Your sign-in has expired. Sign in again, then try again.";
743
+ if (code === "PGRST116") return "It is no longer there \u2014 it may have been deleted.";
744
+ if (code === "23505") return "Something with the same name or key already exists.";
745
+ if (code === "23503") return "It refers to something that no longer exists.";
746
+ if (code === "23514" || code === "23502" || code === "22P02") return "The database would not accept that value.";
747
+ if (code === "57014") return "The database took too long to answer.";
748
+ if (code === "40001" || code === "40P01") return "It changed while you were working. Reload to see the latest.";
749
+ return "The database refused it.";
750
+ }
751
+ function assertWriteLanded(rows, technical) {
752
+ const n = Array.isArray(rows) ? rows.length : rows == null ? 0 : 1;
753
+ if (n > 0) return;
754
+ throw new WriteRefusedError({
755
+ status: 403,
756
+ serverMessage: "Nothing was saved: you may not have permission to change this, or it no longer exists.",
757
+ technical: `${technical} matched 0 rows`
758
+ });
759
+ }
760
+ function refusalSentence(status, serverMessage) {
761
+ const said = serverMessage && !LOOKS_TECHNICAL.test(serverMessage) ? serverMessage : null;
762
+ if (status === 401) return "Your sign-in has expired. Sign in again, then try again.";
763
+ if (status === 403) return said ?? "You do not have permission to do this.";
764
+ if (status === 404) return said ?? "It is no longer there \u2014 it may have been deleted.";
765
+ if (status === 409 || status === 412) return said ?? "It changed while you were working. Reload to see the latest.";
766
+ if (status >= 500) return "The server refused the request.";
767
+ if (said) return `The server said: ${said.replace(/\.?$/, ".")}`;
768
+ return "The server did not accept it.";
769
+ }
770
+ function describeWriteFailure(err, { action, remedy = "Try again." }) {
771
+ const title = `Could not ${action}.`;
772
+ let why;
773
+ if (err instanceof WriteRefusedError) {
774
+ why = err.message;
775
+ } else if (isSerializedRefusal(err)) {
776
+ why = /[.!?]["”']?$/.test(err.message.trim()) ? err.message.trim() : `${err.message.trim()}.`;
777
+ } else if (isPostgrestShaped(err)) {
778
+ why = personSentence(err.message) ?? postgrestSentence(err.code);
779
+ } else if (err && typeof err === "object" && "status" in err && typeof err.status === "number") {
780
+ const e = err;
781
+ why = refusalSentence(e.status, typeof e.userMessage === "string" ? e.userMessage : null);
782
+ } else if (err instanceof TypeError || err instanceof Error && /Failed to fetch|NetworkError|Load failed/i.test(err.message)) {
783
+ why = "The server could not be reached. Check your connection.";
784
+ } else if (err instanceof Error && err.message && !LOOKS_TECHNICAL.test(err.message)) {
785
+ why = err.message.replace(/\.?$/, ".");
786
+ } else if (typeof err === "string" && err.trim() && !LOOKS_TECHNICAL.test(err)) {
787
+ why = err.trim().replace(/\.?$/, ".");
788
+ } else {
789
+ why = "Something went wrong on our side.";
790
+ }
791
+ return { title, description: `${why} ${remedy}`.trim() };
792
+ }
793
+
794
+ // src/db/write-one.ts
795
+ var import_supabase_js = require("@supabase/supabase-js");
796
+ var PAST = {
797
+ save: "saved",
798
+ update: "updated",
799
+ rename: "renamed",
800
+ move: "moved",
801
+ delete: "deleted",
802
+ remove: "removed",
803
+ archive: "archived",
804
+ restore: "restored",
805
+ publish: "published",
806
+ unpublish: "unpublished",
807
+ change: "changed"
808
+ };
809
+ var GERUND = {
810
+ save: "saving",
811
+ update: "updating",
812
+ rename: "renaming",
813
+ move: "moving",
814
+ delete: "deleting",
815
+ remove: "removing",
816
+ archive: "archiving",
817
+ restore: "restoring",
818
+ publish: "publishing",
819
+ unpublish: "unpublishing",
820
+ change: "changing"
821
+ };
822
+ var WriteDidNotLandError = class extends WriteRefusedError {
823
+ action;
824
+ noun;
825
+ reason;
826
+ constructor({ action, noun }, reason = "refused") {
827
+ super({
828
+ status: reason === "taken" ? 409 : 403,
829
+ serverMessage: reason === "taken" ? `This ${noun} was already handled \u2014 refresh to see the latest.` : `Nothing was ${PAST[action]}: this ${noun} no longer exists, or your access does not allow ${GERUND[action]} it.`,
830
+ technical: reason === "taken" ? `writeOne(${action} ${noun}) compare-and-set matched 0 rows \u2014 another writer got there first` : `writeOne(${action} ${noun}) matched 0 rows \u2014 RLS refused or the row is gone`
831
+ });
832
+ this.name = "WriteDidNotLandError";
833
+ this.action = action;
834
+ this.noun = noun;
835
+ this.reason = reason;
836
+ }
837
+ };
838
+ async function writeOne(write, options) {
839
+ const { data, error } = await write;
840
+ if (error) throw error;
841
+ if (Array.isArray(data) && data.length > 0) return data[0];
842
+ if (options.alreadyDone) {
843
+ const current = await options.alreadyDone.reread();
844
+ if (current.error) throw current.error;
845
+ const row = Array.isArray(current.data) ? current.data[0] : current.data;
846
+ if (row && options.alreadyDone.isDone(row)) return row;
847
+ }
848
+ throw new WriteDidNotLandError(options, options.compareAndSet ? "taken" : "refused");
849
+ }
850
+ async function tryWriteOne(write, options) {
851
+ try {
852
+ return { row: await writeOne(write, options), error: null };
853
+ } catch (caught) {
854
+ if (caught instanceof Error) return { row: null, error: caught };
855
+ if (caught && typeof caught === "object" && "code" in caught && typeof caught.code === "string" && "message" in caught && typeof caught.message === "string") {
856
+ return { row: null, error: new import_supabase_js.PostgrestError({
857
+ code: caught.code,
858
+ message: caught.message,
859
+ details: "details" in caught && typeof caught.details === "string" ? caught.details : "",
860
+ hint: "hint" in caught && typeof caught.hint === "string" ? caught.hint : ""
861
+ }) };
862
+ }
863
+ const message = caught && typeof caught === "object" && "message" in caught && typeof caught.message === "string" ? caught.message : String(caught);
864
+ const error = new Error(message);
865
+ error.cause = caught;
866
+ return { row: null, error };
867
+ }
868
+ }
869
+ function writeFailureStatus(error) {
870
+ if (error instanceof WriteDidNotLandError) return error.status;
871
+ if (error && typeof error === "object" && "code" in error) {
872
+ if (error.code === "42501") return 403;
873
+ if (error.code === "PGRST301" || error.code === "PGRST302" || error.code === "PGRST303") return 401;
874
+ if (error.code === "PGRST116") return 404;
875
+ if (error.code === "23505" || error.code === "40001" || error.code === "40P01") return 409;
876
+ }
877
+ return 500;
878
+ }
879
+ var BulkWriteError = class extends Error {
880
+ failures;
881
+ attempted;
882
+ constructor(failures, attempted) {
883
+ super(
884
+ `${failures.length} of ${attempted} ${attempted === 1 ? "change was" : "changes were"} not saved. ${failures[0]?.message ?? ""}`.trim()
885
+ );
886
+ this.name = "BulkWriteError";
887
+ this.failures = failures;
888
+ this.attempted = attempted;
889
+ }
890
+ };
891
+ async function writeOneRow(write, options) {
892
+ const { data, error } = await write;
893
+ if (error) {
894
+ if (error instanceof import_supabase_js.PostgrestError) return { data: null, error };
895
+ const e = error;
896
+ return {
897
+ data: null,
898
+ error: new import_supabase_js.PostgrestError({
899
+ message: typeof e.message === "string" ? e.message : String(error),
900
+ details: typeof e.details === "string" ? e.details : "",
901
+ hint: typeof e.hint === "string" ? e.hint : "",
902
+ code: typeof e.code === "string" ? e.code : ""
903
+ })
904
+ };
905
+ }
906
+ const rows = Array.isArray(data) ? data : [];
907
+ if (rows.length === 1) return { data: rows[0], error: null };
908
+ const refusal = new WriteDidNotLandError(options, options.compareAndSet ? "taken" : "refused");
909
+ return {
910
+ data: null,
911
+ error: new import_supabase_js.PostgrestError({
912
+ message: rows.length === 0 ? refusal.serverMessage ?? refusal.message : `${rows.length} records matched where one ${options.noun} was expected, and all of them were changed. Reload to see the result.`,
913
+ details: rows.length === 0 ? refusal.technical : `writeOneRow(${options.action} ${options.noun}) matched ${rows.length} rows`,
914
+ hint: "",
915
+ code: "PGRST116"
916
+ })
917
+ };
918
+ }
919
+
920
+ // src/db/record-unavailable.ts
921
+ var RecordUnavailableError = class extends Error {
922
+ entity;
923
+ reason;
924
+ recordId;
925
+ /**
926
+ * Canonical entity token, when the read site knows it. With a token AND a
927
+ * recordId the renderer stops describing the ambiguity and ASKS the platform
928
+ * instead — an access gate resolves which of the four it really is.
929
+ * Without one, honest ambiguity is still the best available answer.
930
+ */
931
+ token;
932
+ /** Diagnostics identity for reconciling this exact capture in place. */
933
+ captureId;
934
+ /** Last truth written to diagnostics; avoids duplicate reconciliation. */
935
+ captureResolution;
936
+ constructor(input) {
937
+ super(recordUnavailableMessage(input.entity, input.reason));
938
+ this.name = "RecordUnavailableError";
939
+ this.entity = input.entity;
940
+ this.reason = input.reason;
941
+ this.recordId = input.recordId;
942
+ this.token = input.token;
943
+ this.captureResolution = input.reason;
944
+ }
945
+ };
946
+ function recordUnavailableMessage(entity, reason) {
947
+ if (reason === "deleted") {
948
+ return `This ${entity} was deleted, so it can no longer be opened.`;
949
+ }
950
+ return `We couldn't open this ${entity}. It may have been deleted, or it may belong to an organization you don't have access to.`;
951
+ }
952
+ function isRecordUnavailableError(value) {
953
+ return value instanceof RecordUnavailableError;
954
+ }
678
955
  //# sourceMappingURL=db.cjs.map