@kiwa-test/nextjs 1.0.5 → 1.2.0

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/index.js CHANGED
@@ -241,19 +241,560 @@ async function invokeParallelRoutes(opts) {
241
241
  }
242
242
  return { tree, slotResults, childrenError, layoutError };
243
243
  }
244
+
245
+ // src/setup-next-rsc-env.ts
246
+ var RSC_ERROR_BOUNDARY_SYMBOL = /* @__PURE__ */ Symbol.for("kiwa.next.rsc.errorBoundary");
247
+ var DEFAULT_STREAMING_TIMEOUT_MS = 5e3;
248
+ function buildErrorBoundary(error) {
249
+ return { [RSC_ERROR_BOUNDARY_SYMBOL]: true, error };
250
+ }
251
+ async function runWithTimeout(promise, timeoutMs) {
252
+ let timer;
253
+ const timeout = new Promise((resolve) => {
254
+ timer = setTimeout(() => resolve({ value: null, timedOut: true }), timeoutMs);
255
+ });
256
+ try {
257
+ const value = await Promise.race([
258
+ promise.then((v) => ({ value: v, timedOut: false })),
259
+ timeout
260
+ ]);
261
+ return value;
262
+ } finally {
263
+ if (timer) clearTimeout(timer);
264
+ }
265
+ }
266
+ async function collectStream(source, fallback) {
267
+ const chunks = [];
268
+ if (typeof fallback !== "undefined" && fallback !== null) {
269
+ chunks.push(fallback);
270
+ }
271
+ try {
272
+ for await (const chunk of source) {
273
+ chunks.push(chunk);
274
+ }
275
+ } catch (err) {
276
+ return { chunks, resolved: null, thrown: err };
277
+ }
278
+ const resolved = chunks.length > 0 ? chunks[chunks.length - 1] ?? null : null;
279
+ const fallbackOnly = chunks.length === 1 && fallback !== null && Object.is(chunks[0], fallback);
280
+ return { chunks, resolved: fallbackOnly ? null : resolved, thrown: void 0 };
281
+ }
282
+ async function* singleChunkSource(component, props) {
283
+ const result = await component(props);
284
+ yield result;
285
+ }
286
+ async function setupNextRscEnv(opts = {}) {
287
+ const timeoutMs = opts.streamingTimeout ?? DEFAULT_STREAMING_TIMEOUT_MS;
288
+ const fallback = opts.suspenseFallback ?? null;
289
+ if (timeoutMs <= 0 && (opts.dataSource || opts.component)) {
290
+ return {
291
+ chunks: fallback !== null ? [fallback] : [],
292
+ fallback,
293
+ resolved: null,
294
+ errorBoundary: null,
295
+ timedOut: true
296
+ };
297
+ }
298
+ if (typeof opts.injectError !== "undefined") {
299
+ return {
300
+ chunks: fallback !== null ? [fallback] : [],
301
+ fallback,
302
+ resolved: null,
303
+ errorBoundary: buildErrorBoundary(opts.injectError),
304
+ timedOut: false
305
+ };
306
+ }
307
+ if (!opts.dataSource && !opts.component) {
308
+ return {
309
+ chunks: fallback !== null ? [fallback] : [],
310
+ fallback,
311
+ resolved: null,
312
+ errorBoundary: null,
313
+ timedOut: false
314
+ };
315
+ }
316
+ const source = opts.dataSource ? opts.dataSource : singleChunkSource(opts.component, opts.props ?? {});
317
+ const { value, timedOut } = await runWithTimeout(collectStream(source, fallback), timeoutMs);
318
+ if (timedOut || value === null) {
319
+ return {
320
+ chunks: fallback !== null ? [fallback] : [],
321
+ fallback,
322
+ resolved: null,
323
+ errorBoundary: null,
324
+ timedOut: true
325
+ };
326
+ }
327
+ const { chunks, resolved, thrown } = value;
328
+ if (typeof thrown !== "undefined") {
329
+ return {
330
+ chunks,
331
+ fallback,
332
+ resolved: null,
333
+ errorBoundary: buildErrorBoundary(thrown),
334
+ timedOut: false
335
+ };
336
+ }
337
+ return {
338
+ chunks,
339
+ fallback,
340
+ resolved,
341
+ errorBoundary: null,
342
+ timedOut: false
343
+ };
344
+ }
345
+
346
+ // src/semantics/types.ts
347
+ var dialect = {
348
+ "app-router": {
349
+ "action.form_submitted": "app.server-action.form.submit",
350
+ "action.revalidate_path": "app.cache.revalidatePath",
351
+ "action.revalidate_tag": "app.cache.revalidateTag",
352
+ "action.redirected": "app.navigation.redirect",
353
+ "ppr.static_shell_rendered": "app.ppr.static-shell",
354
+ "ppr.dynamic_hole_opened": "app.ppr.dynamic-hole",
355
+ "ppr.streaming_boundary_flushed": "app.ppr.stream-boundary",
356
+ "ppr.completed": "app.ppr.complete",
357
+ "intercept.current_segment": "app.intercept.current",
358
+ "intercept.parent_segment": "app.intercept.parent",
359
+ "intercept.root_catchall": "app.intercept.root",
360
+ "intercept.modal_opened": "app.intercept.modal",
361
+ "parallel.default_rendered": "app.parallel.default",
362
+ "parallel.loading_rendered": "app.parallel.loading",
363
+ "parallel.error_boundary_captured": "app.parallel.error",
364
+ "parallel.slot_navigated": "app.parallel.navigate"
365
+ },
366
+ "pages-router": {
367
+ "action.form_submitted": "pages.api.form.submit",
368
+ "action.revalidate_path": "pages.isr.revalidate",
369
+ "action.revalidate_tag": "pages.cache.tag.noop",
370
+ "action.redirected": "pages.router.redirect",
371
+ "ppr.static_shell_rendered": "pages.ssg.shell",
372
+ "ppr.dynamic_hole_opened": "pages.ssr.dynamic-hole",
373
+ "ppr.streaming_boundary_flushed": "pages.stream.boundary",
374
+ "ppr.completed": "pages.render.complete",
375
+ "intercept.current_segment": "pages.intercept.current",
376
+ "intercept.parent_segment": "pages.intercept.parent",
377
+ "intercept.root_catchall": "pages.intercept.root",
378
+ "intercept.modal_opened": "pages.modal.open",
379
+ "parallel.default_rendered": "pages.slot.default",
380
+ "parallel.loading_rendered": "pages.slot.loading",
381
+ "parallel.error_boundary_captured": "pages.slot.error",
382
+ "parallel.slot_navigated": "pages.slot.navigate"
383
+ },
384
+ "edge-runtime": {
385
+ "action.form_submitted": "edge.action.form.submit",
386
+ "action.revalidate_path": "edge.cache.revalidatePath",
387
+ "action.revalidate_tag": "edge.cache.revalidateTag",
388
+ "action.redirected": "edge.response.redirect",
389
+ "ppr.static_shell_rendered": "edge.ppr.static-shell",
390
+ "ppr.dynamic_hole_opened": "edge.ppr.dynamic-hole",
391
+ "ppr.streaming_boundary_flushed": "edge.stream.boundary",
392
+ "ppr.completed": "edge.ppr.complete",
393
+ "intercept.current_segment": "edge.intercept.current",
394
+ "intercept.parent_segment": "edge.intercept.parent",
395
+ "intercept.root_catchall": "edge.intercept.root",
396
+ "intercept.modal_opened": "edge.modal.open",
397
+ "parallel.default_rendered": "edge.parallel.default",
398
+ "parallel.loading_rendered": "edge.parallel.loading",
399
+ "parallel.error_boundary_captured": "edge.parallel.error",
400
+ "parallel.slot_navigated": "edge.parallel.navigate"
401
+ }
402
+ };
403
+ function providerEventName(target, neutral) {
404
+ return dialect[target][neutral] ?? neutral;
405
+ }
406
+
407
+ // src/semantics/server-action-advanced.ts
408
+ function startServerActionAdvanced(input) {
409
+ if (input.actionId.length === 0) {
410
+ throw new Error("startServerActionAdvanced: actionId must not be empty");
411
+ }
412
+ return {
413
+ target: input.target,
414
+ actionId: input.actionId,
415
+ state: "idle",
416
+ form: {},
417
+ revalidatedPaths: [],
418
+ revalidatedTags: [],
419
+ redirectUrl: null,
420
+ history: []
421
+ };
422
+ }
423
+ function submitFormAction(session, form) {
424
+ if (session.state !== "idle") {
425
+ throw new Error(`submitFormAction: session is ${session.state}, not idle`);
426
+ }
427
+ session.state = "submitted";
428
+ session.form = { ...form };
429
+ return emit(session, "action.form_submitted", {
430
+ fields: Object.keys(form).join(","),
431
+ fieldCount: Object.keys(form).length
432
+ });
433
+ }
434
+ function revalidateActionPath(session, path) {
435
+ if (session.state === "idle") {
436
+ throw new Error("revalidateActionPath: form action was not submitted");
437
+ }
438
+ if (!path.startsWith("/")) {
439
+ throw new Error("revalidateActionPath: path must start with /");
440
+ }
441
+ session.state = "path-revalidated";
442
+ session.revalidatedPaths.push(path);
443
+ return emit(session, "action.revalidate_path", { path, count: session.revalidatedPaths.length });
444
+ }
445
+ function revalidateActionTag(session, tag) {
446
+ if (session.state === "idle") {
447
+ throw new Error("revalidateActionTag: form action was not submitted");
448
+ }
449
+ if (tag.length === 0) {
450
+ throw new Error("revalidateActionTag: tag must not be empty");
451
+ }
452
+ session.state = "tag-revalidated";
453
+ session.revalidatedTags.push(tag);
454
+ return emit(session, "action.revalidate_tag", { tag, count: session.revalidatedTags.length });
455
+ }
456
+ function redirectAction(session, url) {
457
+ if (session.state === "idle") {
458
+ throw new Error("redirectAction: form action was not submitted");
459
+ }
460
+ if (url.length === 0) {
461
+ throw new Error("redirectAction: url must not be empty");
462
+ }
463
+ session.state = "redirected";
464
+ session.redirectUrl = url;
465
+ return emit(session, "action.redirected", { url });
466
+ }
467
+ function emit(session, neutralEvent, metadata) {
468
+ const step = {
469
+ neutralEvent,
470
+ providerEvent: providerEventName(session.target, neutralEvent),
471
+ state: session.state,
472
+ amountCents: 0,
473
+ metadata: { target: session.target, actionId: session.actionId, ...metadata }
474
+ };
475
+ session.history.push(step);
476
+ return step;
477
+ }
478
+
479
+ // src/semantics/partial-prerendering.ts
480
+ function startPartialPrerendering(input) {
481
+ if (input.routeId.length === 0) {
482
+ throw new Error("startPartialPrerendering: routeId must not be empty");
483
+ }
484
+ return {
485
+ target: input.target,
486
+ routeId: input.routeId,
487
+ state: "idle",
488
+ shellHtml: null,
489
+ dynamicHoles: /* @__PURE__ */ new Map(),
490
+ streamedBoundaries: [],
491
+ history: []
492
+ };
493
+ }
494
+ function renderStaticShell(session, html) {
495
+ if (session.state !== "idle") {
496
+ throw new Error(`renderStaticShell: session is ${session.state}, not idle`);
497
+ }
498
+ if (html.length === 0) {
499
+ throw new Error("renderStaticShell: html must not be empty");
500
+ }
501
+ session.state = "static-shell";
502
+ session.shellHtml = html;
503
+ return emit2(session, "ppr.static_shell_rendered", { bytes: html.length });
504
+ }
505
+ function openDynamicHole(session, input) {
506
+ if (session.shellHtml === null) {
507
+ throw new Error("openDynamicHole: static shell must be rendered first");
508
+ }
509
+ if (input.holeId.length === 0) {
510
+ throw new Error("openDynamicHole: holeId must not be empty");
511
+ }
512
+ session.state = "dynamic-hole";
513
+ session.dynamicHoles.set(input.holeId, input.fallback);
514
+ return emit2(session, "ppr.dynamic_hole_opened", {
515
+ holeId: input.holeId,
516
+ fallback: input.fallback,
517
+ holeCount: session.dynamicHoles.size
518
+ });
519
+ }
520
+ function flushStreamingBoundary(session, input) {
521
+ if (!session.dynamicHoles.has(input.holeId)) {
522
+ throw new Error(`flushStreamingBoundary: ${input.holeId} is not an open dynamic hole`);
523
+ }
524
+ if (input.html.length === 0) {
525
+ throw new Error("flushStreamingBoundary: html must not be empty");
526
+ }
527
+ session.state = "streaming";
528
+ session.streamedBoundaries.push(input.holeId);
529
+ session.dynamicHoles.set(input.holeId, input.html);
530
+ return emit2(session, "ppr.streaming_boundary_flushed", {
531
+ holeId: input.holeId,
532
+ bytes: input.html.length
533
+ });
534
+ }
535
+ function completePartialPrerendering(session) {
536
+ if (session.shellHtml === null) {
537
+ throw new Error("completePartialPrerendering: static shell was not rendered");
538
+ }
539
+ session.state = "completed";
540
+ return emit2(session, "ppr.completed", {
541
+ holeCount: session.dynamicHoles.size,
542
+ streamedCount: session.streamedBoundaries.length
543
+ });
544
+ }
545
+ function emit2(session, neutralEvent, metadata) {
546
+ const step = {
547
+ neutralEvent,
548
+ providerEvent: providerEventName(session.target, neutralEvent),
549
+ state: session.state,
550
+ amountCents: 0,
551
+ metadata: { target: session.target, routeId: session.routeId, ...metadata }
552
+ };
553
+ session.history.push(step);
554
+ return step;
555
+ }
556
+
557
+ // src/semantics/interception-routes.ts
558
+ function startInterceptionRoutes(input) {
559
+ if (input.routeId.length === 0) {
560
+ throw new Error("startInterceptionRoutes: routeId must not be empty");
561
+ }
562
+ return {
563
+ target: input.target,
564
+ routeId: input.routeId,
565
+ state: "idle",
566
+ matches: [],
567
+ modalRoute: null,
568
+ history: []
569
+ };
570
+ }
571
+ function interceptCurrentSegment(session, from, to) {
572
+ return intercept(session, "(.)", "current", "intercept.current_segment", from, to);
573
+ }
574
+ function interceptParentSegment(session, from, to) {
575
+ return intercept(session, "(..)", "parent", "intercept.parent_segment", from, to);
576
+ }
577
+ function interceptRootCatchall(session, from, to) {
578
+ return intercept(session, "(...)", "root", "intercept.root_catchall", from, to);
579
+ }
580
+ function openInterceptedModal(session, modalRoute) {
581
+ if (modalRoute.length === 0) {
582
+ throw new Error("openInterceptedModal: modalRoute must not be empty");
583
+ }
584
+ if (session.matches.length === 0) {
585
+ throw new Error("openInterceptedModal: an interception match is required first");
586
+ }
587
+ session.state = "modal-open";
588
+ session.modalRoute = modalRoute;
589
+ return emit3(session, "intercept.modal_opened", {
590
+ modalRoute,
591
+ matchCount: session.matches.length
592
+ });
593
+ }
594
+ function intercept(session, matcher, state, neutralEvent, from, to) {
595
+ if (!from.startsWith("/") || !to.startsWith("/")) {
596
+ throw new Error("intercept: from and to must start with /");
597
+ }
598
+ session.state = state;
599
+ session.matches.push({ matcher, from, to });
600
+ return emit3(session, neutralEvent, { matcher, from, to });
601
+ }
602
+ function emit3(session, neutralEvent, metadata) {
603
+ const step = {
604
+ neutralEvent,
605
+ providerEvent: providerEventName(session.target, neutralEvent),
606
+ state: session.state,
607
+ amountCents: 0,
608
+ metadata: { target: session.target, routeId: session.routeId, ...metadata }
609
+ };
610
+ session.history.push(step);
611
+ return step;
612
+ }
613
+
614
+ // src/semantics/parallel-routes-advanced.ts
615
+ function startParallelRoutesAdvanced(input) {
616
+ if (input.layoutId.length === 0) {
617
+ throw new Error("startParallelRoutesAdvanced: layoutId must not be empty");
618
+ }
619
+ return {
620
+ target: input.target,
621
+ layoutId: input.layoutId,
622
+ state: "idle",
623
+ slots: /* @__PURE__ */ new Map(),
624
+ loadingSlots: /* @__PURE__ */ new Set(),
625
+ errors: [],
626
+ history: []
627
+ };
628
+ }
629
+ function renderDefaultSlot(session, slot, html) {
630
+ assertSlot(slot);
631
+ session.state = "default-rendered";
632
+ session.slots.set(slot, html);
633
+ return emit4(session, "parallel.default_rendered", { slot, html });
634
+ }
635
+ function renderLoadingState(session, slot) {
636
+ assertSlot(slot);
637
+ session.state = "loading-rendered";
638
+ session.loadingSlots.add(slot);
639
+ return emit4(session, "parallel.loading_rendered", {
640
+ slot,
641
+ loadingCount: session.loadingSlots.size
642
+ });
643
+ }
644
+ function captureParallelError(session, input) {
645
+ assertSlot(input.slot);
646
+ const message = typeof input.error === "string" ? input.error : input.error.message;
647
+ session.state = "error-captured";
648
+ session.errors.push({ slot: input.slot, message });
649
+ return emit4(session, "parallel.error_boundary_captured", {
650
+ slot: input.slot,
651
+ message,
652
+ errorCount: session.errors.length
653
+ });
654
+ }
655
+ function navigateSlot(session, input) {
656
+ assertSlot(input.slot);
657
+ if (!input.from.startsWith("/") || !input.to.startsWith("/")) {
658
+ throw new Error("navigateSlot: from and to must start with /");
659
+ }
660
+ session.state = "slot-navigated";
661
+ session.loadingSlots.delete(input.slot);
662
+ return emit4(session, "parallel.slot_navigated", input);
663
+ }
664
+ function assertSlot(slot) {
665
+ if (slot.length === 0) {
666
+ throw new Error("slot must not be empty");
667
+ }
668
+ }
669
+ function emit4(session, neutralEvent, metadata) {
670
+ const step = {
671
+ neutralEvent,
672
+ providerEvent: providerEventName(session.target, neutralEvent),
673
+ state: session.state,
674
+ amountCents: 0,
675
+ metadata: { target: session.target, layoutId: session.layoutId, ...metadata }
676
+ };
677
+ session.history.push(step);
678
+ return step;
679
+ }
680
+
681
+ // src/semantics/fidelity.ts
682
+ var NEXT_AXIS_TO_EVENTS = {
683
+ "server-action-advanced": [
684
+ "action.form_submitted",
685
+ "action.revalidate_path",
686
+ "action.revalidate_tag",
687
+ "action.redirected"
688
+ ],
689
+ "partial-prerendering": [
690
+ "ppr.static_shell_rendered",
691
+ "ppr.dynamic_hole_opened",
692
+ "ppr.streaming_boundary_flushed",
693
+ "ppr.completed"
694
+ ],
695
+ "interception-routes": [
696
+ "intercept.current_segment",
697
+ "intercept.parent_segment",
698
+ "intercept.root_catchall",
699
+ "intercept.modal_opened"
700
+ ],
701
+ "parallel-routes-advanced": [
702
+ "parallel.default_rendered",
703
+ "parallel.loading_rendered",
704
+ "parallel.error_boundary_captured",
705
+ "parallel.slot_navigated"
706
+ ]
707
+ };
708
+ function collectFidelityCoverage(providers = ["app-router", "pages-router", "edge-runtime"]) {
709
+ const axes = Object.keys(NEXT_AXIS_TO_EVENTS);
710
+ const rows = [];
711
+ for (const provider of providers) {
712
+ for (const axis of axes) {
713
+ const neutralEvents = NEXT_AXIS_TO_EVENTS[axis];
714
+ rows.push({
715
+ provider,
716
+ axis,
717
+ neutralEvents,
718
+ providerEvents: neutralEvents.map((event) => providerEventName(provider, event))
719
+ });
720
+ }
721
+ }
722
+ return { providers, axes, rows };
723
+ }
724
+
725
+ // src/real-driver.ts
726
+ var TARGET_KEY_ENV = {
727
+ "app-router": "NEXT_APP_URL",
728
+ "pages-router": "NEXT_PAGES_URL",
729
+ "edge-runtime": "EDGE_RUNTIME_URL"
730
+ };
731
+ function resolveMode(provider, env = process.env) {
732
+ const rawMode = env.KIWA_MODE?.toLowerCase();
733
+ if (rawMode !== void 0 && rawMode !== "real" && rawMode !== "mock") {
734
+ return { provider, mode: "mock", reason: "invalid-mode" };
735
+ }
736
+ if (rawMode !== "real") {
737
+ return { provider, mode: "mock", reason: "default-mock" };
738
+ }
739
+ const keyValue = env[TARGET_KEY_ENV[provider]];
740
+ if (typeof keyValue !== "string" || keyValue.length === 0) {
741
+ return { provider, mode: "mock", reason: "missing-key" };
742
+ }
743
+ return { provider, mode: "real", reason: "kiwa-mode-real" };
744
+ }
745
+ function resolveAllModes(env = process.env) {
746
+ const providers = ["app-router", "pages-router", "edge-runtime"];
747
+ return providers.map((provider) => resolveMode(provider, env));
748
+ }
749
+ function assertMode(provider, expected, env = process.env) {
750
+ const resolved = resolveMode(provider, env);
751
+ if (resolved.mode !== expected) {
752
+ throw new Error(
753
+ `expected ${provider} in ${expected} mode but resolved ${resolved.mode} (${resolved.reason})`
754
+ );
755
+ }
756
+ }
244
757
  export {
245
758
  FORBIDDEN_SYMBOL,
246
759
  MIDDLEWARE_ACTION_SYMBOL,
760
+ NEXT_AXIS_TO_EVENTS,
247
761
  NOT_FOUND_SYMBOL,
248
762
  PARALLEL_INTERCEPTION_SYMBOL,
249
763
  REDIRECT_SYMBOL,
764
+ RSC_ERROR_BOUNDARY_SYMBOL,
250
765
  RSC_REDIRECT_SYMBOL,
766
+ assertMode,
767
+ captureParallelError,
768
+ collectFidelityCoverage,
769
+ completePartialPrerendering,
251
770
  findAll,
771
+ flushStreamingBoundary,
772
+ interceptCurrentSegment,
773
+ interceptParentSegment,
774
+ interceptRootCatchall,
252
775
  invokeMiddleware,
253
776
  invokeParallelRoutes,
254
777
  invokeServerAction,
255
778
  middlewareActions,
779
+ navigateSlot,
780
+ openDynamicHole,
781
+ openInterceptedModal,
782
+ providerEventName,
783
+ redirectAction,
784
+ renderDefaultSlot,
785
+ renderLoadingState,
256
786
  renderServerComponent,
787
+ renderStaticShell,
788
+ resolveAllModes,
789
+ resolveMode,
790
+ revalidateActionPath,
791
+ revalidateActionTag,
792
+ setupNextRscEnv,
793
+ startInterceptionRoutes,
794
+ startParallelRoutesAdvanced,
795
+ startPartialPrerendering,
796
+ startServerActionAdvanced,
797
+ submitFormAction,
257
798
  textContent
258
799
  };
259
800
  //# sourceMappingURL=index.js.map