supastash 0.2.9 → 0.2.10

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.
@@ -1 +1 @@
1
- {"version":3,"file":"uploadChunk.d.ts","sourceRoot":"","sources":["../../../../../src/desktop/utils/sync/pushLocal/uploadChunk.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,WAAW,EAAE,MAAM,sCAAsC,CAAC;AAgMnE;;;;GAIG;AACH,wBAAsB,UAAU,CAC9B,KAAK,EAAE,MAAM,EACb,eAAe,EAAE,WAAW,EAAE,EAC9B,cAAc,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE,KAAK,OAAO,CAAC,OAAO,CAAC,iBAetD"}
1
+ {"version":3,"file":"uploadChunk.d.ts","sourceRoot":"","sources":["../../../../../src/desktop/utils/sync/pushLocal/uploadChunk.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,WAAW,EAAE,MAAM,sCAAsC,CAAC;AA6OnE;;;;GAIG;AACH,wBAAsB,UAAU,CAC9B,KAAK,EAAE,MAAM,EACb,eAAe,EAAE,WAAW,EAAE,EAC9B,cAAc,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE,KAAK,OAAO,CAAC,OAAO,CAAC,iBAetD"}
@@ -1,5 +1,6 @@
1
1
  import { DEFAULT_CHUNK_SIZE } from "../../../../shared/constants/syncDefaults";
2
2
  import { getSupastashConfig } from "../../../../shared/core/config";
3
+ import { isOnline } from "../../../../shared/utils/connection";
3
4
  import { normalizeForSupabase } from "../../../../shared/utils/getSafeValues";
4
5
  import log from "../../../../shared/utils/logs";
5
6
  import { supabaseClientErr } from "../../../../shared/utils/supabaseClientErr";
@@ -28,6 +29,7 @@ async function uploadChunk(table, chunk, onPushToRemote) {
28
29
  const hasRPCPath = !!config.pushRPCPath;
29
30
  const ids = chunk.map((row) => row.id);
30
31
  const toPush = [];
32
+ let remoteExistsMap = new Map();
31
33
  // If we have a RPC path, we can push the whole chunk. Server validates freshness.
32
34
  if (hasRPCPath) {
33
35
  toPush.push(...chunk);
@@ -35,6 +37,8 @@ async function uploadChunk(table, chunk, onPushToRemote) {
35
37
  else {
36
38
  // Fetch remote data for the current chunk
37
39
  const remoteIds = await fetchRemoteHeadsChunked(table, ids, supabase);
40
+ for (const id of ids)
41
+ remoteExistsMap.set(id, remoteIds.has(id));
38
42
  // Loop through the initial chunk and check if the id is in the remote data
39
43
  const filtered = filterRowsByUpdatedAt(table, chunk, remoteIds);
40
44
  toPush.push(...filtered);
@@ -70,7 +74,7 @@ async function uploadChunk(table, chunk, onPushToRemote) {
70
74
  let batchOk = false;
71
75
  // RPC return values
72
76
  let completed = [];
73
- let existsMap = new Map();
77
+ let existsMap = new Map(remoteExistsMap);
74
78
  if (onPushToRemote) {
75
79
  const ok = await onPushToRemote(pending);
76
80
  if (ok)
@@ -82,12 +86,43 @@ async function uploadChunk(table, chunk, onPushToRemote) {
82
86
  pending = [...res.data.skipped];
83
87
  existsMap = res.data.existsMap;
84
88
  batchOk = res.error == null && pending.length === 0;
85
- // If there was an RPC error, we need to retry the main function
86
89
  if (res.error) {
87
- attempts++;
88
- await backoff(attempts);
89
- pending = [...preflightOK];
90
- continue;
90
+ if (!(await isOnline())) {
91
+ attempts++;
92
+ await backoff(attempts);
93
+ pending = [...preflightOK];
94
+ continue;
95
+ }
96
+ // Online: RPC failed — run per-row single upserts immediately, no retry.
97
+ // pending was reassigned to res.data.skipped (empty on error), so use preflightOK.
98
+ const rowsToProcess = [...preflightOK];
99
+ try {
100
+ const heads = await fetchRemoteHeadsChunked(table, rowsToProcess.map((r) => r.id), supabase);
101
+ for (const r of rowsToProcess)
102
+ existsMap.set(r.id, heads.has(r.id));
103
+ }
104
+ catch {
105
+ // existsMap stays empty — singleUpsert will fall back to upsert
106
+ }
107
+ const syncedNow = [];
108
+ const keep = [];
109
+ for (const row of rowsToProcess) {
110
+ const rowRes = await singleUpsert(table, row, supabase, existsMap);
111
+ if (!rowRes.error) {
112
+ syncedNow.push(row.id);
113
+ continue;
114
+ }
115
+ errorCount++;
116
+ lastError = rowRes.error;
117
+ const decision = await handleRowFailure(config, table, row, rowRes.error, supabase);
118
+ if (decision !== "KEEP")
119
+ continue;
120
+ keep.push(row);
121
+ }
122
+ if (syncedNow.length)
123
+ await markSynced(table, syncedNow);
124
+ pending = keep;
125
+ break;
91
126
  }
92
127
  }
93
128
  else {
@@ -115,7 +150,7 @@ async function uploadChunk(table, chunk, onPushToRemote) {
115
150
  res = await rpcUpsertSingle({ table, row, supabase, existsMap });
116
151
  }
117
152
  else {
118
- res = await singleUpsert(table, row, supabase);
153
+ res = await singleUpsert(table, row, supabase, existsMap);
119
154
  }
120
155
  if (!res.error) {
121
156
  syncedNow.push(row.id);
@@ -133,10 +168,18 @@ async function uploadChunk(table, chunk, onPushToRemote) {
133
168
  await markSynced(table, syncedNow);
134
169
  if (keep.length === 0)
135
170
  return;
136
- // Backoff before next batch round (exponential, bounded by policy)
137
- attempts++;
138
- await backoff(attempts);
139
- pending = keep;
171
+ if (!(await isOnline())) {
172
+ attempts++;
173
+ await backoff(attempts);
174
+ pending = keep;
175
+ }
176
+ else {
177
+ // Online: errors are genuine failures, not network issues — don't retry
178
+ for (const r of keep)
179
+ setQueryStatus(r.id, table, "error");
180
+ pending = keep; // update pending so post-loop markLogError reflects only true failures
181
+ break;
182
+ }
140
183
  }
141
184
  if (pending.length > 0) {
142
185
  SyncInfoUpdater.markLogError({
@@ -2,7 +2,7 @@ import { SupastashConfig } from "../../../../shared/types/supastashConfig.types"
2
2
  import { RowLike } from "../../../../shared/types/syncEngine.types";
3
3
  export declare function classifyFailure(cfg: SupastashConfig<any>, code?: string | number): "HTTP" | "UNKNOWN" | "NON_RETRYABLE" | "FK_BLOCK" | "UNIQUE_VIOLATION" | "RETRYABLE";
4
4
  declare function batchUpsert(table: string, rows: RowLike[], supabase: any): Promise<any>;
5
- declare function singleUpsert(table: string, row: RowLike, supabase: any): Promise<any>;
5
+ declare function singleUpsert(table: string, row: RowLike, supabase: any, existsMap?: Map<string, boolean>): Promise<any>;
6
6
  declare function backoff(attempts: number): Promise<void>;
7
7
  declare function rpcUpsert({ table, rows, supabase, }: {
8
8
  table: string;
@@ -1 +1 @@
1
- {"version":3,"file":"uploadHelpers.d.ts","sourceRoot":"","sources":["../../../../../src/desktop/utils/sync/pushLocal/uploadHelpers.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,eAAe,EAAE,MAAM,gDAAgD,CAAC;AACjF,OAAO,EAAE,OAAO,EAAE,MAAM,2CAA2C,CAAC;AAOpE,wBAAgB,eAAe,CAC7B,GAAG,EAAE,eAAe,CAAC,GAAG,CAAC,EACzB,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,wFAYvB;AAED,iBAAe,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE,QAAQ,EAAE,GAAG,gBAEvE;AAED,iBAAe,YAAY,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,OAAO,EAAE,QAAQ,EAAE,GAAG,gBAErE;AAED,iBAAe,OAAO,CAAC,QAAQ,EAAE,MAAM,iBAOtC;AAmBD,iBAAe,SAAS,CAAC,EACvB,KAAK,EACL,IAAI,EACJ,QAAQ,GACT,EAAE;IACD,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,OAAO,EAAE,CAAC;IAChB,QAAQ,EAAE,GAAG,CAAC;CACf;;;;;;;GA+CA;AAED,iBAAe,eAAe,CAAC,EAC7B,KAAK,EACL,GAAG,EACH,QAAQ,EACR,SAAS,GACV,EAAE;IACD,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,OAAO,CAAC;IACb,QAAQ,EAAE,GAAG,CAAC;IACd,SAAS,EAAE,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACjC;;;;;;GAaA;AAMD,iBAAe,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,iBAIrD;AAWD,iBAAS,qBAAqB,CAC5B,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,OAAO,EAAE,EAChB,WAAW,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,aAiCjC;AAMD,iBAAe,gBAAgB,CAC7B,GAAG,EAAE,eAAe,CAAC,GAAG,CAAC,EACzB,KAAK,EAAE,MAAM,EACb,GAAG,EAAE,OAAO,EACZ,GAAG,EAAE,GAAG,EACR,QAAQ,EAAE,GAAG,GACZ,OAAO,CAAC,MAAM,GAAG,MAAM,GAAG,UAAU,CAAC,CA4DvC;AAgBD,OAAO,EACL,OAAO,EACP,WAAW,EACX,qBAAqB,EACrB,gBAAgB,EAChB,UAAU,EACV,SAAS,EACT,eAAe,EACf,YAAY,GACb,CAAC;AAEF;;;GAGG;AACH,wBAAsB,kBAAkB,CACtC,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,GAAG,iBAkBd;AASD,wBAAsB,uBAAuB,CAC3C,KAAK,EAAE,MAAM,EACb,GAAG,EAAE,MAAM,EAAE,EACb,QAAQ,EAAE,GAAG,gCAcd"}
1
+ {"version":3,"file":"uploadHelpers.d.ts","sourceRoot":"","sources":["../../../../../src/desktop/utils/sync/pushLocal/uploadHelpers.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,eAAe,EAAE,MAAM,gDAAgD,CAAC;AACjF,OAAO,EAAE,OAAO,EAAE,MAAM,2CAA2C,CAAC;AAOpE,wBAAgB,eAAe,CAC7B,GAAG,EAAE,eAAe,CAAC,GAAG,CAAC,EACzB,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,wFAYvB;AAED,iBAAe,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE,QAAQ,EAAE,GAAG,gBAEvE;AAED,iBAAe,YAAY,CACzB,KAAK,EAAE,MAAM,EACb,GAAG,EAAE,OAAO,EACZ,QAAQ,EAAE,GAAG,EACb,SAAS,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,gBAwBjC;AAED,iBAAe,OAAO,CAAC,QAAQ,EAAE,MAAM,iBAOtC;AAmBD,iBAAe,SAAS,CAAC,EACvB,KAAK,EACL,IAAI,EACJ,QAAQ,GACT,EAAE;IACD,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,OAAO,EAAE,CAAC;IAChB,QAAQ,EAAE,GAAG,CAAC;CACf;;;;;;;GA+CA;AAED,iBAAe,eAAe,CAAC,EAC7B,KAAK,EACL,GAAG,EACH,QAAQ,EACR,SAAS,GACV,EAAE;IACD,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,OAAO,CAAC;IACb,QAAQ,EAAE,GAAG,CAAC;IACd,SAAS,EAAE,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACjC;;;;;;GAaA;AAMD,iBAAe,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,iBAIrD;AAWD,iBAAS,qBAAqB,CAC5B,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,OAAO,EAAE,EAChB,WAAW,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,aAiCjC;AAMD,iBAAe,gBAAgB,CAC7B,GAAG,EAAE,eAAe,CAAC,GAAG,CAAC,EACzB,KAAK,EAAE,MAAM,EACb,GAAG,EAAE,OAAO,EACZ,GAAG,EAAE,GAAG,EACR,QAAQ,EAAE,GAAG,GACZ,OAAO,CAAC,MAAM,GAAG,MAAM,GAAG,UAAU,CAAC,CA4DvC;AAgBD,OAAO,EACL,OAAO,EACP,WAAW,EACX,qBAAqB,EACrB,gBAAgB,EAChB,UAAU,EACV,SAAS,EACT,eAAe,EACf,YAAY,GACb,CAAC;AAEF;;;GAGG;AACH,wBAAsB,kBAAkB,CACtC,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,GAAG,iBAkBd;AASD,wBAAsB,uBAAuB,CAC3C,KAAK,EAAE,MAAM,EACb,GAAG,EAAE,MAAM,EAAE,EACb,QAAQ,EAAE,GAAG,gCAcd"}
@@ -26,8 +26,30 @@ export function classifyFailure(cfg, code) {
26
26
  async function batchUpsert(table, rows, supabase) {
27
27
  return await supabase.from(table).upsert(rows);
28
28
  }
29
- async function singleUpsert(table, row, supabase) {
30
- return await supabase.from(table).upsert(row).select("id").maybeSingle();
29
+ async function singleUpsert(table, row, supabase, existsMap) {
30
+ const exists = existsMap?.get(row.id);
31
+ if (exists === true) {
32
+ const { data, error } = await supabase
33
+ .from(table)
34
+ .update(row)
35
+ .eq("id", row.id)
36
+ .select("id")
37
+ .maybeSingle();
38
+ if (!error)
39
+ return { data, error: null };
40
+ // RLS may block update — fall through to upsert
41
+ }
42
+ else if (exists === false) {
43
+ const { data, error } = await supabase
44
+ .from(table)
45
+ .insert(row)
46
+ .select("id")
47
+ .maybeSingle();
48
+ if (!error)
49
+ return { data, error: null };
50
+ // RLS may block insert — fall through to upsert
51
+ }
52
+ return supabase.from(table).upsert(row).select("id").maybeSingle();
31
53
  }
32
54
  async function backoff(attempts) {
33
55
  const config = getSupastashConfig();
@@ -1 +1 @@
1
- {"version":3,"file":"uploadChunk.d.ts","sourceRoot":"","sources":["../../../../../src/native/utils/sync/pushLocal/uploadChunk.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,WAAW,EAAE,MAAM,sCAAsC,CAAC;AAmMnE;;;;GAIG;AACH,wBAAsB,UAAU,CAC9B,KAAK,EAAE,MAAM,EACb,eAAe,EAAE,WAAW,EAAE,EAC9B,cAAc,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE,KAAK,OAAO,CAAC,OAAO,CAAC,iBAetD"}
1
+ {"version":3,"file":"uploadChunk.d.ts","sourceRoot":"","sources":["../../../../../src/native/utils/sync/pushLocal/uploadChunk.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,WAAW,EAAE,MAAM,sCAAsC,CAAC;AAgPnE;;;;GAIG;AACH,wBAAsB,UAAU,CAC9B,KAAK,EAAE,MAAM,EACb,eAAe,EAAE,WAAW,EAAE,EAC9B,cAAc,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE,KAAK,OAAO,CAAC,OAAO,CAAC,iBAetD"}
@@ -32,6 +32,7 @@ async function uploadChunk(table, chunk, onPushToRemote) {
32
32
  const hasRPCPath = !!config.pushRPCPath;
33
33
  const ids = chunk.map((row) => row.id);
34
34
  const toPush = [];
35
+ let remoteExistsMap = new Map();
35
36
  // If we have a RPC path, we can push the whole chunk. Server validates freshness.
36
37
  if (hasRPCPath) {
37
38
  toPush.push(...chunk);
@@ -39,6 +40,8 @@ async function uploadChunk(table, chunk, onPushToRemote) {
39
40
  else {
40
41
  // Fetch remote data for the current chunk
41
42
  const remoteIds = await fetchRemoteHeadsChunked(table, ids, supabase);
43
+ for (const id of ids)
44
+ remoteExistsMap.set(id, remoteIds.has(id));
42
45
  // Loop through the initial chunk and check if the id is in the remote data
43
46
  const filtered = filterRowsByUpdatedAt(table, chunk, remoteIds);
44
47
  toPush.push(...filtered);
@@ -74,7 +77,7 @@ async function uploadChunk(table, chunk, onPushToRemote) {
74
77
  let batchOk = false;
75
78
  // RPC return values
76
79
  let completed = [];
77
- let existsMap = new Map();
80
+ let existsMap = new Map(remoteExistsMap);
78
81
  if (onPushToRemote) {
79
82
  const ok = await onPushToRemote(pending);
80
83
  if (ok)
@@ -86,12 +89,43 @@ async function uploadChunk(table, chunk, onPushToRemote) {
86
89
  pending = [...res.data.skipped];
87
90
  existsMap = res.data.existsMap;
88
91
  batchOk = res.error == null && pending.length === 0;
89
- // If there was an RPC error, we need to retry the main function
90
92
  if (res.error) {
91
- attempts++;
92
- await backoff(attempts);
93
- pending = [...preflightOK];
94
- continue;
93
+ if (!(await isOnline())) {
94
+ attempts++;
95
+ await backoff(attempts);
96
+ pending = [...preflightOK];
97
+ continue;
98
+ }
99
+ // Online: RPC failed — run per-row single upserts immediately, no retry.
100
+ // pending was reassigned to res.data.skipped (empty on error), so use preflightOK.
101
+ const rowsToProcess = [...preflightOK];
102
+ try {
103
+ const heads = await fetchRemoteHeadsChunked(table, rowsToProcess.map((r) => r.id), supabase);
104
+ for (const r of rowsToProcess)
105
+ existsMap.set(r.id, heads.has(r.id));
106
+ }
107
+ catch {
108
+ // existsMap stays empty — singleUpsert will fall back to upsert
109
+ }
110
+ const syncedNow = [];
111
+ const keep = [];
112
+ for (const row of rowsToProcess) {
113
+ const rowRes = await singleUpsert(table, row, supabase, existsMap);
114
+ if (!rowRes.error) {
115
+ syncedNow.push(row.id);
116
+ continue;
117
+ }
118
+ errorCount++;
119
+ lastError = rowRes.error;
120
+ const decision = await handleRowFailure(config, table, row, rowRes.error, supabase);
121
+ if (decision !== "KEEP")
122
+ continue;
123
+ keep.push(row);
124
+ }
125
+ if (syncedNow.length)
126
+ await markSynced(table, syncedNow);
127
+ pending = keep;
128
+ break;
95
129
  }
96
130
  }
97
131
  else {
@@ -119,7 +153,7 @@ async function uploadChunk(table, chunk, onPushToRemote) {
119
153
  res = await rpcUpsertSingle({ table, row, supabase, existsMap });
120
154
  }
121
155
  else {
122
- res = await singleUpsert(table, row, supabase);
156
+ res = await singleUpsert(table, row, supabase, existsMap);
123
157
  }
124
158
  if (!res.error) {
125
159
  syncedNow.push(row.id);
@@ -137,10 +171,18 @@ async function uploadChunk(table, chunk, onPushToRemote) {
137
171
  await markSynced(table, syncedNow);
138
172
  if (keep.length === 0)
139
173
  return;
140
- // Backoff before next batch round (exponential, bounded by policy)
141
- attempts++;
142
- await backoff(attempts);
143
- pending = keep;
174
+ if (!(await isOnline())) {
175
+ attempts++;
176
+ await backoff(attempts);
177
+ pending = keep;
178
+ }
179
+ else {
180
+ // Online: errors are genuine failures, not network issues — don't retry
181
+ for (const r of keep)
182
+ setQueryStatus(r.id, table, "error");
183
+ pending = keep; // update pending so post-loop markLogError reflects only true failures
184
+ break;
185
+ }
144
186
  }
145
187
  if (pending.length > 0) {
146
188
  SyncInfoUpdater.markLogError({
@@ -2,7 +2,7 @@ import { SupastashConfig } from "../../../../shared/types/supastashConfig.types"
2
2
  import { RowLike } from "../../../../shared/types/syncEngine.types";
3
3
  export declare function classifyFailure(cfg: SupastashConfig<any>, code?: string | number): "HTTP" | "UNKNOWN" | "NON_RETRYABLE" | "FK_BLOCK" | "UNIQUE_VIOLATION" | "RETRYABLE";
4
4
  declare function batchUpsert(table: string, rows: RowLike[], supabase: any): Promise<any>;
5
- declare function singleUpsert(table: string, row: RowLike, supabase: any): Promise<any>;
5
+ declare function singleUpsert(table: string, row: RowLike, supabase: any, existsMap?: Map<string, boolean>): Promise<any>;
6
6
  declare function backoff(attempts: number): Promise<void>;
7
7
  declare function rpcUpsert({ table, rows, supabase, }: {
8
8
  table: string;
@@ -1 +1 @@
1
- {"version":3,"file":"uploadHelpers.d.ts","sourceRoot":"","sources":["../../../../../src/native/utils/sync/pushLocal/uploadHelpers.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,eAAe,EAAE,MAAM,gDAAgD,CAAC;AACjF,OAAO,EAAE,OAAO,EAAE,MAAM,2CAA2C,CAAC;AAOpE,wBAAgB,eAAe,CAC7B,GAAG,EAAE,eAAe,CAAC,GAAG,CAAC,EACzB,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,wFAYvB;AAED,iBAAe,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE,QAAQ,EAAE,GAAG,gBAEvE;AAED,iBAAe,YAAY,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,OAAO,EAAE,QAAQ,EAAE,GAAG,gBAErE;AAED,iBAAe,OAAO,CAAC,QAAQ,EAAE,MAAM,iBAOtC;AAmBD,iBAAe,SAAS,CAAC,EACvB,KAAK,EACL,IAAI,EACJ,QAAQ,GACT,EAAE;IACD,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,OAAO,EAAE,CAAC;IAChB,QAAQ,EAAE,GAAG,CAAC;CACf;;;;;;;GA+CA;AAED,iBAAe,eAAe,CAAC,EAC7B,KAAK,EACL,GAAG,EACH,QAAQ,EACR,SAAS,GACV,EAAE;IACD,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,OAAO,CAAC;IACb,QAAQ,EAAE,GAAG,CAAC;IACd,SAAS,EAAE,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACjC;;;;;;GAaA;AAMD,iBAAe,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,iBAIrD;AAWD,iBAAS,qBAAqB,CAC5B,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,OAAO,EAAE,EAChB,WAAW,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,aAiCjC;AAMD,iBAAe,gBAAgB,CAC7B,GAAG,EAAE,eAAe,CAAC,GAAG,CAAC,EACzB,KAAK,EAAE,MAAM,EACb,GAAG,EAAE,OAAO,EACZ,GAAG,EAAE,GAAG,EACR,QAAQ,EAAE,GAAG,GACZ,OAAO,CAAC,MAAM,GAAG,MAAM,GAAG,UAAU,CAAC,CA4DvC;AAgBD,OAAO,EACL,OAAO,EACP,WAAW,EACX,qBAAqB,EACrB,gBAAgB,EAChB,UAAU,EACV,SAAS,EACT,eAAe,EACf,YAAY,GACb,CAAC;AAEF;;;GAGG;AACH,wBAAsB,kBAAkB,CACtC,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,GAAG,iBAkBd;AASD,wBAAsB,uBAAuB,CAC3C,KAAK,EAAE,MAAM,EACb,GAAG,EAAE,MAAM,EAAE,EACb,QAAQ,EAAE,GAAG,gCAcd"}
1
+ {"version":3,"file":"uploadHelpers.d.ts","sourceRoot":"","sources":["../../../../../src/native/utils/sync/pushLocal/uploadHelpers.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,eAAe,EAAE,MAAM,gDAAgD,CAAC;AACjF,OAAO,EAAE,OAAO,EAAE,MAAM,2CAA2C,CAAC;AAOpE,wBAAgB,eAAe,CAC7B,GAAG,EAAE,eAAe,CAAC,GAAG,CAAC,EACzB,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,wFAYvB;AAED,iBAAe,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE,QAAQ,EAAE,GAAG,gBAEvE;AAED,iBAAe,YAAY,CACzB,KAAK,EAAE,MAAM,EACb,GAAG,EAAE,OAAO,EACZ,QAAQ,EAAE,GAAG,EACb,SAAS,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,gBAwBjC;AAED,iBAAe,OAAO,CAAC,QAAQ,EAAE,MAAM,iBAOtC;AAmBD,iBAAe,SAAS,CAAC,EACvB,KAAK,EACL,IAAI,EACJ,QAAQ,GACT,EAAE;IACD,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,OAAO,EAAE,CAAC;IAChB,QAAQ,EAAE,GAAG,CAAC;CACf;;;;;;;GA+CA;AAED,iBAAe,eAAe,CAAC,EAC7B,KAAK,EACL,GAAG,EACH,QAAQ,EACR,SAAS,GACV,EAAE;IACD,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,OAAO,CAAC;IACb,QAAQ,EAAE,GAAG,CAAC;IACd,SAAS,EAAE,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACjC;;;;;;GAaA;AAMD,iBAAe,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,iBAIrD;AAWD,iBAAS,qBAAqB,CAC5B,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,OAAO,EAAE,EAChB,WAAW,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,aAiCjC;AAMD,iBAAe,gBAAgB,CAC7B,GAAG,EAAE,eAAe,CAAC,GAAG,CAAC,EACzB,KAAK,EAAE,MAAM,EACb,GAAG,EAAE,OAAO,EACZ,GAAG,EAAE,GAAG,EACR,QAAQ,EAAE,GAAG,GACZ,OAAO,CAAC,MAAM,GAAG,MAAM,GAAG,UAAU,CAAC,CA4DvC;AAgBD,OAAO,EACL,OAAO,EACP,WAAW,EACX,qBAAqB,EACrB,gBAAgB,EAChB,UAAU,EACV,SAAS,EACT,eAAe,EACf,YAAY,GACb,CAAC;AAEF;;;GAGG;AACH,wBAAsB,kBAAkB,CACtC,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,GAAG,iBAkBd;AASD,wBAAsB,uBAAuB,CAC3C,KAAK,EAAE,MAAM,EACb,GAAG,EAAE,MAAM,EAAE,EACb,QAAQ,EAAE,GAAG,gCAcd"}
@@ -26,8 +26,30 @@ export function classifyFailure(cfg, code) {
26
26
  async function batchUpsert(table, rows, supabase) {
27
27
  return await supabase.from(table).upsert(rows);
28
28
  }
29
- async function singleUpsert(table, row, supabase) {
30
- return await supabase.from(table).upsert(row).select("id").maybeSingle();
29
+ async function singleUpsert(table, row, supabase, existsMap) {
30
+ const exists = existsMap?.get(row.id);
31
+ if (exists === true) {
32
+ const { data, error } = await supabase
33
+ .from(table)
34
+ .update(row)
35
+ .eq("id", row.id)
36
+ .select("id")
37
+ .maybeSingle();
38
+ if (!error)
39
+ return { data, error: null };
40
+ // RLS may block update — fall through to upsert
41
+ }
42
+ else if (exists === false) {
43
+ const { data, error } = await supabase
44
+ .from(table)
45
+ .insert(row)
46
+ .select("id")
47
+ .maybeSingle();
48
+ if (!error)
49
+ return { data, error: null };
50
+ // RLS may block insert — fall through to upsert
51
+ }
52
+ return supabase.from(table).upsert(row).select("id").maybeSingle();
31
53
  }
32
54
  async function backoff(attempts) {
33
55
  const config = getSupastashConfig();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "supastash",
3
- "version": "0.2.9",
3
+ "version": "0.2.10",
4
4
  "main": "./dist/index.js",
5
5
  "types": "./dist/index.d.ts",
6
6
  "type": "module",