@flowselections/floriday-voorraad 1.0.29 → 1.0.31
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/dist-lib/components/voorraad/StockSyncStatus.d.ts.map +1 -1
- package/dist-lib/components/voorraad/StockSyncStatus.js +20 -3
- package/dist-lib/components/voorraad/VoorraadPage.d.ts.map +1 -1
- package/dist-lib/components/voorraad/VoorraadPage.js +1 -4
- package/dist-lib/hooks/useStockSyncHealth.d.ts.map +1 -1
- package/dist-lib/hooks/useStockSyncHealth.js +7 -1
- package/dist-lib/integrations/supabase/auth-middleware.d.ts +9404 -6816
- package/dist-lib/integrations/supabase/auth-middleware.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/client.d.ts +9404 -6816
- package/dist-lib/integrations/supabase/client.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/client.server.d.ts +9404 -6816
- package/dist-lib/integrations/supabase/client.server.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/types.d.ts +9658 -7030
- package/dist-lib/integrations/supabase/types.d.ts.map +1 -1
- package/dist-lib/lib/floricode-required-features.functions.d.ts +9404 -6816
- package/dist-lib/lib/floricode-required-features.functions.d.ts.map +1 -1
- package/dist-lib/lib/floriday-customer-offer.functions.d.ts +9404 -6816
- package/dist-lib/lib/floriday-customer-offer.functions.d.ts.map +1 -1
- package/dist-lib/lib/floriday-env.functions.d.ts +5819 -643
- package/dist-lib/lib/floriday-env.functions.d.ts.map +1 -1
- package/dist-lib/lib/floriday-gateway.functions.d.ts +17319 -1791
- package/dist-lib/lib/floriday-gateway.functions.d.ts.map +1 -1
- package/dist-lib/lib/floriday-stock-supply.server.d.ts +53 -0
- package/dist-lib/lib/floriday-stock-supply.server.d.ts.map +1 -0
- package/dist-lib/lib/floriday-stock-supply.server.js +151 -0
- package/dist-lib/lib/floriday-vbn-required.functions.d.ts +5819 -643
- package/dist-lib/lib/floriday-vbn-required.functions.d.ts.map +1 -1
- package/dist-lib/lib/floriday-warehouse.functions.d.ts +8694 -930
- package/dist-lib/lib/floriday-warehouse.functions.d.ts.map +1 -1
- package/dist-lib/lib/floriday-writes.functions.d.ts +5819 -643
- package/dist-lib/lib/floriday-writes.functions.d.ts.map +1 -1
- package/dist-lib/lib/logistics-tasks.server.d.ts +17 -0
- package/dist-lib/lib/logistics-tasks.server.d.ts.map +1 -0
- package/dist-lib/lib/logistics-tasks.server.js +303 -0
- package/dist-lib/lib/module-runtime-config.functions.d.ts +1 -13
- package/dist-lib/lib/module-runtime-config.functions.d.ts.map +1 -1
- package/dist-lib/lib/module-runtime-config.functions.js +17 -93
- package/dist-lib/lib/module-runtime.server.d.ts +33 -0
- package/dist-lib/lib/module-runtime.server.d.ts.map +1 -0
- package/dist-lib/lib/module-runtime.server.js +184 -0
- package/dist-lib/lib/stock-sync.functions.d.ts +20445 -0
- package/dist-lib/lib/stock-sync.functions.d.ts.map +1 -0
- package/dist-lib/lib/stock-sync.functions.js +30 -0
- package/dist-lib/lib/supabase-admin.server.d.ts +15 -0
- package/dist-lib/lib/supabase-admin.server.d.ts.map +1 -0
- package/dist-lib/lib/supabase-admin.server.js +35 -0
- package/package.json +2 -2
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export declare const TASK_TYPE = "stock.reserve";
|
|
2
|
+
export declare const MAX_ATTEMPTS = 8;
|
|
3
|
+
export declare class PermanentTaskError extends Error {
|
|
4
|
+
readonly permanent = true;
|
|
5
|
+
}
|
|
6
|
+
export interface RunSummary {
|
|
7
|
+
claimed: number;
|
|
8
|
+
done: number;
|
|
9
|
+
retried: number;
|
|
10
|
+
failed: number;
|
|
11
|
+
shadow: number;
|
|
12
|
+
skipped: number;
|
|
13
|
+
durationMs: number;
|
|
14
|
+
}
|
|
15
|
+
export declare function runLogisticsTasks(): Promise<RunSummary>;
|
|
16
|
+
export declare function maybeTickLogisticsTasks(): void;
|
|
17
|
+
//# sourceMappingURL=logistics-tasks.server.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logistics-tasks.server.d.ts","sourceRoot":"","sources":["../../src/lib/logistics-tasks.server.ts"],"names":[],"mappings":"AA0BA,eAAO,MAAM,SAAS,kBAAkB,CAAC;AACzC,eAAO,MAAM,YAAY,IAAI,CAAC;AAQ9B,qBAAa,kBAAmB,SAAQ,KAAK;IAC3C,QAAQ,CAAC,SAAS,QAAQ;CAC3B;AA0BD,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;CACpB;AA0FD,wBAAsB,iBAAiB,IAAI,OAAO,CAAC,UAAU,CAAC,CAuJ7D;AAgBD,wBAAgB,uBAAuB,IAAI,IAAI,CA2B9C"}
|
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Achtergrondverwerking van Logistics Engine-taken (`stock.reserve`).
|
|
3
|
+
*
|
|
4
|
+
* De Engine plant; deze module voert uit. Er is GEEN UI meer: een cron roept
|
|
5
|
+
* elke minuut de hook `/api/public/hooks/logistics-tasks-run` aan, die deze
|
|
6
|
+
* runner start.
|
|
7
|
+
*
|
|
8
|
+
* Bulletproof-eigenschappen:
|
|
9
|
+
* - Atomair claimen (`claim_logistics_tasks`, FOR UPDATE SKIP LOCKED), dus
|
|
10
|
+
* nooit dubbele uitvoering bij overlappende runs of meerdere instanties.
|
|
11
|
+
* - Vastgelopen taken (`in_progress` ouder dan 5 min) worden vanzelf herclaimd.
|
|
12
|
+
* - Exponentiële backoff met jitter bij tijdelijke fouten; permanente fouten
|
|
13
|
+
* stoppen direct.
|
|
14
|
+
* - Idempotent: reserveringen worden op een absolute waarde gezet, nooit
|
|
15
|
+
* opgeteld. Een herhaalde poging kan dus niet dubbeltellen.
|
|
16
|
+
* - Harde tijdslimiet per run zodat de worker nooit zijn limiet overschrijdt.
|
|
17
|
+
*
|
|
18
|
+
* Server-only: dit bestand mag nooit vanuit componenten geïmporteerd worden.
|
|
19
|
+
*/
|
|
20
|
+
import { OWNER_MODULE, describeLoadCarriers, parseStockReservePlan, } from "./logistics-engine";
|
|
21
|
+
export const TASK_TYPE = "stock.reserve";
|
|
22
|
+
export const MAX_ATTEMPTS = 8;
|
|
23
|
+
const BATCH_SIZE = 25;
|
|
24
|
+
const STALE_MINUTES = 5;
|
|
25
|
+
const RUN_BUDGET_MS = 25000;
|
|
26
|
+
/** Wachttijd (minuten) per poging; laatste waarde geldt voor alle latere. */
|
|
27
|
+
const BACKOFF_MINUTES = [1, 2, 4, 8, 15, 30];
|
|
28
|
+
const ATTEMPT_LOG_LIMIT = 20;
|
|
29
|
+
export class PermanentTaskError extends Error {
|
|
30
|
+
constructor() {
|
|
31
|
+
super(...arguments);
|
|
32
|
+
this.permanent = true;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
function backoffMs(attempt) {
|
|
36
|
+
const idx = Math.min(Math.max(attempt - 1, 0), BACKOFF_MINUTES.length - 1);
|
|
37
|
+
const base = BACKOFF_MINUTES[idx] * 60000;
|
|
38
|
+
// Jitter (±20%) voorkomt dat veel taken tegelijk opnieuw losbarsten.
|
|
39
|
+
const jitter = base * 0.2 * (Math.random() * 2 - 1);
|
|
40
|
+
return Math.max(15000, Math.round(base + jitter));
|
|
41
|
+
}
|
|
42
|
+
function isPermanent(error) {
|
|
43
|
+
if (error instanceof PermanentTaskError)
|
|
44
|
+
return true;
|
|
45
|
+
const message = String(error?.message ?? error ?? "").toLowerCase();
|
|
46
|
+
return (message.includes("geen voorraadrij") ||
|
|
47
|
+
message.includes("ongeldige payload") ||
|
|
48
|
+
message.includes("stale_connection") ||
|
|
49
|
+
message.includes("no_active_floriday_connection"));
|
|
50
|
+
}
|
|
51
|
+
function errorMessage(error) {
|
|
52
|
+
const raw = error?.message ?? String(error ?? "onbekende fout");
|
|
53
|
+
return String(raw).slice(0, 500);
|
|
54
|
+
}
|
|
55
|
+
function appendAttempt(task, entry) {
|
|
56
|
+
const current = Array.isArray(task.attempt_log)
|
|
57
|
+
? task.attempt_log
|
|
58
|
+
: [];
|
|
59
|
+
return [...current, entry].slice(-ATTEMPT_LOG_LIMIT);
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Voert één geclaimde taak uit. Gooit bij mislukking; de caller bepaalt
|
|
63
|
+
* of er opnieuw geprobeerd wordt.
|
|
64
|
+
*/
|
|
65
|
+
async function executeTask(supabaseAdmin, task, connectionId) {
|
|
66
|
+
const plan = parseStockReservePlan(task.payload);
|
|
67
|
+
if (plan.lines.length === 0) {
|
|
68
|
+
return {
|
|
69
|
+
applied: false,
|
|
70
|
+
diff: [],
|
|
71
|
+
message: `Taak zonder regels (${describeLoadCarriers(plan.loadCarriers)}).`,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
const keys = plan.lines.map((l) => l.tradeItemKey);
|
|
75
|
+
let query = supabaseAdmin
|
|
76
|
+
.from("trade_item_stock")
|
|
77
|
+
.select("trade_item_key,physical_stock,reserved_stock,connection_id")
|
|
78
|
+
.in("trade_item_key", keys);
|
|
79
|
+
if (connectionId)
|
|
80
|
+
query = query.eq("connection_id", connectionId);
|
|
81
|
+
const { data: rows, error: readError } = await query;
|
|
82
|
+
if (readError)
|
|
83
|
+
throw new Error(`voorraad lezen: ${readError.message}`);
|
|
84
|
+
const stock = (rows ?? []);
|
|
85
|
+
const diff = plan.lines.map((line) => {
|
|
86
|
+
const row = stock.find((r) => r.trade_item_key === line.tradeItemKey);
|
|
87
|
+
return {
|
|
88
|
+
tradeItemKey: line.tradeItemKey,
|
|
89
|
+
currentReserved: row?.reserved_stock ?? 0,
|
|
90
|
+
engineReserved: line.quantity,
|
|
91
|
+
delta: line.quantity - (row?.reserved_stock ?? 0),
|
|
92
|
+
};
|
|
93
|
+
});
|
|
94
|
+
if (task.shadow) {
|
|
95
|
+
return {
|
|
96
|
+
applied: false,
|
|
97
|
+
diff,
|
|
98
|
+
message: `Schaduwmodus: ${diff.length} regel(s) vergeleken, niets geschreven (${describeLoadCarriers(plan.loadCarriers)}).`,
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
for (const line of plan.lines) {
|
|
102
|
+
const row = stock.find((r) => r.trade_item_key === line.tradeItemKey);
|
|
103
|
+
if (!row) {
|
|
104
|
+
throw new PermanentTaskError(`Geen voorraadrij voor ${line.tradeItemKey}`);
|
|
105
|
+
}
|
|
106
|
+
// Absolute waarde: idempotent bij een herhaalde poging.
|
|
107
|
+
const { error } = await supabaseAdmin
|
|
108
|
+
.from("trade_item_stock")
|
|
109
|
+
.update({ reserved_stock: line.quantity })
|
|
110
|
+
.eq("connection_id", row.connection_id)
|
|
111
|
+
.eq("trade_item_key", line.tradeItemKey);
|
|
112
|
+
if (error)
|
|
113
|
+
throw new Error(`reservering ${line.tradeItemKey}: ${error.message}`);
|
|
114
|
+
}
|
|
115
|
+
return {
|
|
116
|
+
applied: true,
|
|
117
|
+
diff,
|
|
118
|
+
message: `${plan.lines.length} regel(s) gereserveerd (${describeLoadCarriers(plan.loadCarriers)}).`,
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
export async function runLogisticsTasks() {
|
|
122
|
+
const startedAt = Date.now();
|
|
123
|
+
const summary = {
|
|
124
|
+
claimed: 0,
|
|
125
|
+
done: 0,
|
|
126
|
+
retried: 0,
|
|
127
|
+
failed: 0,
|
|
128
|
+
shadow: 0,
|
|
129
|
+
skipped: 0,
|
|
130
|
+
durationMs: 0,
|
|
131
|
+
};
|
|
132
|
+
const { supabaseAdmin } = await import("../integrations/supabase/client.server");
|
|
133
|
+
// Actieve koppeling is optioneel: zonder koppeling reserveren we op alle
|
|
134
|
+
// rijen van het artikel. Faalt de resolutie, dan blokkeren we niet.
|
|
135
|
+
let connectionId = null;
|
|
136
|
+
try {
|
|
137
|
+
const { resolveActiveFloridayConnection } = await import("./floriday-env.server");
|
|
138
|
+
connectionId = (await resolveActiveFloridayConnection(supabaseAdmin)).id;
|
|
139
|
+
}
|
|
140
|
+
catch {
|
|
141
|
+
connectionId = null;
|
|
142
|
+
}
|
|
143
|
+
const worker = `voorraad-${crypto.randomUUID().slice(0, 8)}`;
|
|
144
|
+
const { data: claimed, error: claimError } = await supabaseAdmin.rpc("claim_logistics_tasks", {
|
|
145
|
+
_owner_module: OWNER_MODULE,
|
|
146
|
+
_task_type: TASK_TYPE,
|
|
147
|
+
_worker: worker,
|
|
148
|
+
_limit: BATCH_SIZE,
|
|
149
|
+
_stale_minutes: STALE_MINUTES,
|
|
150
|
+
});
|
|
151
|
+
if (claimError)
|
|
152
|
+
throw new Error(`taken claimen: ${claimError.message}`);
|
|
153
|
+
const tasks = (claimed ?? []);
|
|
154
|
+
summary.claimed = tasks.length;
|
|
155
|
+
for (const task of tasks) {
|
|
156
|
+
if (Date.now() - startedAt > RUN_BUDGET_MS) {
|
|
157
|
+
// Tijdbudget op: taak teruggeven zodat de volgende run hem oppakt.
|
|
158
|
+
await supabaseAdmin
|
|
159
|
+
.from("logistics_tasks")
|
|
160
|
+
.update({
|
|
161
|
+
status: "planned",
|
|
162
|
+
claimed_by: null,
|
|
163
|
+
claimed_at: null,
|
|
164
|
+
next_attempt_at: new Date().toISOString(),
|
|
165
|
+
updated_at: new Date().toISOString(),
|
|
166
|
+
})
|
|
167
|
+
.eq("id", task.id)
|
|
168
|
+
.eq("claimed_by", worker);
|
|
169
|
+
summary.skipped++;
|
|
170
|
+
continue;
|
|
171
|
+
}
|
|
172
|
+
const attempt = (task.attempts ?? 0) + 1;
|
|
173
|
+
const nowIso = new Date().toISOString();
|
|
174
|
+
try {
|
|
175
|
+
const result = await executeTask(supabaseAdmin, task, connectionId);
|
|
176
|
+
const log = appendAttempt(task, {
|
|
177
|
+
attempt,
|
|
178
|
+
at: nowIso,
|
|
179
|
+
ok: true,
|
|
180
|
+
});
|
|
181
|
+
await supabaseAdmin
|
|
182
|
+
.from("logistics_tasks")
|
|
183
|
+
.update({
|
|
184
|
+
status: "done",
|
|
185
|
+
attempts: attempt,
|
|
186
|
+
attempt_log: log,
|
|
187
|
+
last_error: null,
|
|
188
|
+
error: null,
|
|
189
|
+
next_attempt_at: null,
|
|
190
|
+
claimed_by: null,
|
|
191
|
+
claimed_at: null,
|
|
192
|
+
result: {
|
|
193
|
+
taskId: task.id,
|
|
194
|
+
applied: result.applied,
|
|
195
|
+
shadow: task.shadow,
|
|
196
|
+
diff: result.diff,
|
|
197
|
+
message: result.message,
|
|
198
|
+
},
|
|
199
|
+
completed_at: nowIso,
|
|
200
|
+
updated_at: nowIso,
|
|
201
|
+
})
|
|
202
|
+
.eq("id", task.id);
|
|
203
|
+
if (task.shadow)
|
|
204
|
+
summary.shadow++;
|
|
205
|
+
summary.done++;
|
|
206
|
+
}
|
|
207
|
+
catch (e) {
|
|
208
|
+
const message = errorMessage(e);
|
|
209
|
+
const permanent = isPermanent(e);
|
|
210
|
+
const exhausted = attempt >= MAX_ATTEMPTS;
|
|
211
|
+
const log = appendAttempt(task, {
|
|
212
|
+
attempt,
|
|
213
|
+
at: nowIso,
|
|
214
|
+
ok: false,
|
|
215
|
+
error: message,
|
|
216
|
+
permanent,
|
|
217
|
+
});
|
|
218
|
+
if (permanent || exhausted) {
|
|
219
|
+
await supabaseAdmin
|
|
220
|
+
.from("logistics_tasks")
|
|
221
|
+
.update({
|
|
222
|
+
status: "failed",
|
|
223
|
+
attempts: attempt,
|
|
224
|
+
attempt_log: log,
|
|
225
|
+
last_error: message,
|
|
226
|
+
error: message,
|
|
227
|
+
next_attempt_at: null,
|
|
228
|
+
claimed_by: null,
|
|
229
|
+
claimed_at: null,
|
|
230
|
+
completed_at: nowIso,
|
|
231
|
+
updated_at: nowIso,
|
|
232
|
+
})
|
|
233
|
+
.eq("id", task.id);
|
|
234
|
+
summary.failed++;
|
|
235
|
+
console.error(`[voorraad] taak ${task.id} definitief mislukt (poging ${attempt}): ${message}`);
|
|
236
|
+
}
|
|
237
|
+
else {
|
|
238
|
+
const next = new Date(Date.now() + backoffMs(attempt)).toISOString();
|
|
239
|
+
await supabaseAdmin
|
|
240
|
+
.from("logistics_tasks")
|
|
241
|
+
.update({
|
|
242
|
+
status: "planned",
|
|
243
|
+
attempts: attempt,
|
|
244
|
+
attempt_log: log,
|
|
245
|
+
last_error: message,
|
|
246
|
+
error: message,
|
|
247
|
+
next_attempt_at: next,
|
|
248
|
+
claimed_by: null,
|
|
249
|
+
claimed_at: null,
|
|
250
|
+
updated_at: nowIso,
|
|
251
|
+
})
|
|
252
|
+
.eq("id", task.id);
|
|
253
|
+
summary.retried++;
|
|
254
|
+
console.warn(`[voorraad] taak ${task.id} mislukt (poging ${attempt}), opnieuw om ${next}: ${message}`);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
summary.durationMs = Date.now() - startedAt;
|
|
259
|
+
return summary;
|
|
260
|
+
}
|
|
261
|
+
/**
|
|
262
|
+
* Vangnet zonder cron.
|
|
263
|
+
*
|
|
264
|
+
* Kan pg_net de host (nog) niet bereiken — bijvoorbeeld direct nadat de module
|
|
265
|
+
* in een nieuwe shell is gehangen — dan blijven taken staan. Deze tick wordt
|
|
266
|
+
* fire-and-forget vanuit binnenkomende requests aangeroepen: hoogstens één keer
|
|
267
|
+
* per minuut, en alleen wanneer er daadwerkelijk taken openstaan.
|
|
268
|
+
* Volledig onzichtbaar voor de gebruiker en veilig, omdat de runner atomair
|
|
269
|
+
* claimt en idempotent is.
|
|
270
|
+
*/
|
|
271
|
+
const TICK_THROTTLE_MS = 60000;
|
|
272
|
+
let lastTickAt = 0;
|
|
273
|
+
let tickInFlight = false;
|
|
274
|
+
export function maybeTickLogisticsTasks() {
|
|
275
|
+
const now = Date.now();
|
|
276
|
+
if (tickInFlight || now - lastTickAt < TICK_THROTTLE_MS)
|
|
277
|
+
return;
|
|
278
|
+
lastTickAt = now;
|
|
279
|
+
tickInFlight = true;
|
|
280
|
+
void (async () => {
|
|
281
|
+
try {
|
|
282
|
+
const { supabaseAdmin } = await import("../integrations/supabase/client.server");
|
|
283
|
+
const { count, error } = await supabaseAdmin
|
|
284
|
+
.from("logistics_tasks")
|
|
285
|
+
.select("id", { count: "exact", head: true })
|
|
286
|
+
.eq("owner_module", OWNER_MODULE)
|
|
287
|
+
.eq("task_type", TASK_TYPE)
|
|
288
|
+
.eq("status", "planned")
|
|
289
|
+
.or(`next_attempt_at.is.null,next_attempt_at.lte.${new Date().toISOString()}`);
|
|
290
|
+
if (error)
|
|
291
|
+
throw new Error(error.message);
|
|
292
|
+
if (!count)
|
|
293
|
+
return;
|
|
294
|
+
await runLogisticsTasks();
|
|
295
|
+
}
|
|
296
|
+
catch (e) {
|
|
297
|
+
console.warn("[voorraad] achtergrond-tick mislukt:", e?.message ?? e);
|
|
298
|
+
}
|
|
299
|
+
finally {
|
|
300
|
+
tickInFlight = false;
|
|
301
|
+
}
|
|
302
|
+
})();
|
|
303
|
+
}
|
|
@@ -1,14 +1,2 @@
|
|
|
1
|
-
export
|
|
2
|
-
ok: boolean;
|
|
3
|
-
baseUrl: string | null;
|
|
4
|
-
changed: boolean;
|
|
5
|
-
reason?: string;
|
|
6
|
-
}
|
|
7
|
-
/**
|
|
8
|
-
* Idempotent: schrijft alleen wanneer de waarde daadwerkelijk wijzigt.
|
|
9
|
-
* Bewust zonder auth-middleware zodat de registratie ook tijdens SSR van een
|
|
10
|
-
* publieke shell-pagina kan slagen; er wordt niets gelezen of teruggegeven
|
|
11
|
-
* dat gevoelig is.
|
|
12
|
-
*/
|
|
13
|
-
export declare const registerModuleBaseUrl: import("@tanstack/start-client-core").OptionalFetcher<undefined, undefined, Promise<RegisterBaseUrlResult>>;
|
|
1
|
+
export declare const registerModuleBaseUrl: import("@tanstack/start-client-core").OptionalFetcher<undefined, undefined, Promise<import("./module-runtime.server").ModuleRegistrationResult>>;
|
|
14
2
|
//# sourceMappingURL=module-runtime-config.functions.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"module-runtime-config.functions.d.ts","sourceRoot":"","sources":["../../src/lib/module-runtime-config.functions.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"module-runtime-config.functions.d.ts","sourceRoot":"","sources":["../../src/lib/module-runtime-config.functions.ts"],"names":[],"mappings":"AAWA,eAAO,MAAM,qBAAqB,kJAsBjC,CAAC"}
|
|
@@ -1,107 +1,31 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Server-functie die de module registreert in de database waarin hij draait.
|
|
3
3
|
*
|
|
4
|
-
* De
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* wanneer de module in een andere shell wordt gehangen.
|
|
9
|
-
*
|
|
10
|
-
* Resolutievolgorde:
|
|
11
|
-
* 1. PUBLIC_BASE_URL (expliciet gezet)
|
|
12
|
-
* 2. centrale Auth-module `/api/shell-host`
|
|
13
|
-
* 3. origin van het inkomende request
|
|
4
|
+
* De echte logica staat in `module-runtime.server.ts`. Deze wrapper wordt
|
|
5
|
+
* tijdens SSR aangeroepen (zie `src/routes/__root.tsx`), zodat de cron-hosts
|
|
6
|
+
* kloppen zodra de module in een andere shell wordt gehangen — zonder UI en
|
|
7
|
+
* zonder handmatige instellingen.
|
|
14
8
|
*/
|
|
15
9
|
import { createServerFn } from "@tanstack/react-start";
|
|
16
10
|
import { getRequest } from "@tanstack/react-start/server";
|
|
17
|
-
const
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
if (!trimmed)
|
|
21
|
-
return null;
|
|
22
|
-
try {
|
|
23
|
-
const u = new URL(trimmed);
|
|
24
|
-
if (u.protocol !== "https:" && u.protocol !== "http:")
|
|
25
|
-
return null;
|
|
26
|
-
// Lokale dev-origins nooit registreren: pg_cron kan die niet bereiken.
|
|
27
|
-
const host = u.hostname.toLowerCase();
|
|
28
|
-
if (host === "localhost" || host === "127.0.0.1" || host === "0.0.0.0" || host.endsWith(".local")) {
|
|
29
|
-
return null;
|
|
30
|
-
}
|
|
31
|
-
return `${u.protocol}//${u.host}`;
|
|
32
|
-
}
|
|
33
|
-
catch {
|
|
34
|
-
return null;
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
async function resolveBaseUrl() {
|
|
38
|
-
const explicit = normalizeOrigin(process.env.PUBLIC_BASE_URL);
|
|
39
|
-
if (explicit)
|
|
40
|
-
return explicit;
|
|
41
|
-
// De hooks (/api/public/hooks/...) worden door DEZE module geserveerd,
|
|
42
|
-
// dus de origin van het inkomende request heeft voorrang op de shell-host.
|
|
11
|
+
export const registerModuleBaseUrl = createServerFn({ method: "POST" }).handler(async () => {
|
|
12
|
+
const { registerModuleRuntime } = await import("./module-runtime.server");
|
|
13
|
+
let request = null;
|
|
43
14
|
try {
|
|
44
|
-
|
|
45
|
-
const fromRequest = normalizeOrigin(req?.url ?? null);
|
|
46
|
-
if (fromRequest)
|
|
47
|
-
return fromRequest;
|
|
48
|
-
const forwardedHost = req?.headers?.get("x-forwarded-host") ?? req?.headers?.get("host");
|
|
49
|
-
if (forwardedHost) {
|
|
50
|
-
const proto = req?.headers?.get("x-forwarded-proto") ?? "https";
|
|
51
|
-
const fromHeaders = normalizeOrigin(`${proto}://${forwardedHost}`);
|
|
52
|
-
if (fromHeaders)
|
|
53
|
-
return fromHeaders;
|
|
54
|
-
}
|
|
15
|
+
request = getRequest();
|
|
55
16
|
}
|
|
56
17
|
catch {
|
|
57
|
-
|
|
18
|
+
request = null;
|
|
58
19
|
}
|
|
20
|
+
const result = await registerModuleRuntime(request);
|
|
21
|
+
// Vangnet: mocht de cron (nog) niet aankomen, dan verwerkt de module zelf
|
|
22
|
+
// openstaande taken. Throttled tot maximaal één run per minuut.
|
|
59
23
|
try {
|
|
60
|
-
const {
|
|
61
|
-
|
|
62
|
-
if (host)
|
|
63
|
-
return host;
|
|
24
|
+
const { maybeTickLogisticsTasks } = await import("./logistics-tasks.server");
|
|
25
|
+
maybeTickLogisticsTasks();
|
|
64
26
|
}
|
|
65
27
|
catch {
|
|
66
|
-
//
|
|
67
|
-
}
|
|
68
|
-
return null;
|
|
69
|
-
}
|
|
70
|
-
/**
|
|
71
|
-
* Idempotent: schrijft alleen wanneer de waarde daadwerkelijk wijzigt.
|
|
72
|
-
* Bewust zonder auth-middleware zodat de registratie ook tijdens SSR van een
|
|
73
|
-
* publieke shell-pagina kan slagen; er wordt niets gelezen of teruggegeven
|
|
74
|
-
* dat gevoelig is.
|
|
75
|
-
*/
|
|
76
|
-
export const registerModuleBaseUrl = createServerFn({ method: "POST" }).handler(async () => {
|
|
77
|
-
const baseUrl = await resolveBaseUrl();
|
|
78
|
-
if (!baseUrl) {
|
|
79
|
-
return { ok: false, baseUrl: null, changed: false, reason: "geen origin resolvbaar" };
|
|
80
|
-
}
|
|
81
|
-
try {
|
|
82
|
-
const { supabaseAdmin } = await import("../integrations/supabase/client.server");
|
|
83
|
-
const { data: existing } = await supabaseAdmin
|
|
84
|
-
.from("module_runtime_config")
|
|
85
|
-
.select("value")
|
|
86
|
-
.eq("key", CONFIG_KEY)
|
|
87
|
-
.maybeSingle();
|
|
88
|
-
if (existing?.value === baseUrl) {
|
|
89
|
-
return { ok: true, baseUrl, changed: false };
|
|
90
|
-
}
|
|
91
|
-
const { error } = await supabaseAdmin
|
|
92
|
-
.from("module_runtime_config")
|
|
93
|
-
.upsert({ key: CONFIG_KEY, value: baseUrl }, { onConflict: "key" });
|
|
94
|
-
if (error) {
|
|
95
|
-
return { ok: false, baseUrl, changed: false, reason: error.message };
|
|
96
|
-
}
|
|
97
|
-
return { ok: true, baseUrl, changed: true };
|
|
98
|
-
}
|
|
99
|
-
catch (e) {
|
|
100
|
-
return {
|
|
101
|
-
ok: false,
|
|
102
|
-
baseUrl,
|
|
103
|
-
changed: false,
|
|
104
|
-
reason: e?.message ?? String(e),
|
|
105
|
-
};
|
|
28
|
+
// tick mag nooit de registratie laten falen
|
|
106
29
|
}
|
|
30
|
+
return result;
|
|
107
31
|
});
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Zelfregistratie van de module in de database waarin hij draait.
|
|
3
|
+
*
|
|
4
|
+
* De cron-functies `run_logistics_tasks_dispatch()` en
|
|
5
|
+
* `run_floriday_continuous_stock_sync()` bevatten geen hardgecodeerde host:
|
|
6
|
+
* ze lezen `module_runtime_config.voorraad_base_url` en
|
|
7
|
+
* `module_runtime_config.voorraad_hook_apikey`. Dit bestand schrijft die twee
|
|
8
|
+
* waarden weg zodra de module ergens een request afhandelt, zodat de cron
|
|
9
|
+
* out-of-the-box werkt in elke shell/database waar de module wordt gehangen —
|
|
10
|
+
* zonder handmatige instellingen en zonder iets in de UI.
|
|
11
|
+
*
|
|
12
|
+
* Server-only: nooit importeren vanuit componenten of client-code.
|
|
13
|
+
*/
|
|
14
|
+
/**
|
|
15
|
+
* Resolutie met verificatie: de eerste kandidaat die de module daadwerkelijk
|
|
16
|
+
* bedient wint. Bedient geen enkele kandidaat de module, dan wordt er niets
|
|
17
|
+
* geregistreerd (de bestaande, werkende waarde blijft staan).
|
|
18
|
+
*/
|
|
19
|
+
export declare function resolveModuleBaseUrl(request?: Request | null): Promise<string | null>;
|
|
20
|
+
export interface ModuleRegistrationResult {
|
|
21
|
+
ok: boolean;
|
|
22
|
+
baseUrl: string | null;
|
|
23
|
+
changed: boolean;
|
|
24
|
+
reason?: string;
|
|
25
|
+
}
|
|
26
|
+
/** Schrijft basis-URL + hook-apikey weg (idempotent, schrijft alleen bij wijziging). */
|
|
27
|
+
export declare function registerModuleRuntime(request?: Request | null): Promise<ModuleRegistrationResult>;
|
|
28
|
+
/**
|
|
29
|
+
* Fire-and-forget variant met throttle; veilig om bij élk request aan te roepen.
|
|
30
|
+
* Faalt nooit hard en logt alleen bij een echte fout.
|
|
31
|
+
*/
|
|
32
|
+
export declare function ensureModuleRuntimeRegistered(request?: Request | null): void;
|
|
33
|
+
//# sourceMappingURL=module-runtime.server.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"module-runtime.server.d.ts","sourceRoot":"","sources":["../../src/lib/module-runtime.server.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AA2FH;;;;GAIG;AACH,wBAAsB,oBAAoB,CACxC,OAAO,CAAC,EAAE,OAAO,GAAG,IAAI,GACvB,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAMxB;AAkCD,MAAM,WAAW,wBAAwB;IACvC,EAAE,EAAE,OAAO,CAAC;IACZ,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,wFAAwF;AACxF,wBAAsB,qBAAqB,CACzC,OAAO,CAAC,EAAE,OAAO,GAAG,IAAI,GACvB,OAAO,CAAC,wBAAwB,CAAC,CAyBnC;AAED;;;GAGG;AACH,wBAAgB,6BAA6B,CAAC,OAAO,CAAC,EAAE,OAAO,GAAG,IAAI,GAAG,IAAI,CAsB5E"}
|