git: 3129dbeb5a4e - main - devel/cbang: databases/postgresql-plv8js: Chase v8 upgrade

From: Xin LI <delphij_at_FreeBSD.org>
Date: Fri, 02 Oct 2026 06:09:36 UTC
The branch main has been updated by delphij:

URL: https://cgit.FreeBSD.org/ports/commit/?id=3129dbeb5a4e0ebfcc9997501f3852f22df40b60

commit 3129dbeb5a4e0ebfcc9997501f3852f22df40b60
Author:     Xin LI <delphij@FreeBSD.org>
AuthorDate: 2026-10-02 06:07:47 +0000
Commit:     Xin LI <delphij@FreeBSD.org>
CommitDate: 2026-10-02 06:09:12 +0000

    devel/cbang: databases/postgresql-plv8js: Chase v8 upgrade
    
    V8 15.4 headers refuse to compile ("Non-CppGC MicrotaskQueue definition
    is not supported") unless the embedder defines V8_CPPGC_MICROTASK_QUEUE,
    matching how lang/v8 itself is built.
    
    databases/postgresql-plv8js: pass the define in CXXFLAGS.
    
    devel/cbang: define it in the v8 scons config before the v8.h header
    check, which otherwise fails and silently disables V8 support, breaking
    packaging.  Also record it in cbang/config.h for consumers.
---
 cad/camotics/Makefile                              |   5 +-
 databases/postgresql-plv8js/Makefile               |   4 +-
 databases/postgresql-plv8js/files/patch-Makefile   |  14 +-
 databases/postgresql-plv8js/files/patch-plv8.cc    |  60 +++++++
 databases/postgresql-plv8js/files/patch-plv8.h     |  44 ++++++
 .../files/patch-plv8__allocator.cc                 |  10 ++
 .../files/patch-plv8__allocator.h                  |  11 ++
 .../postgresql-plv8js/files/patch-plv8__func.cc    | 175 +++++++++++++++++++++
 .../postgresql-plv8js/files/patch-plv8__type.cc    |  20 +++
 databases/postgresql-plv8js/pkg-plist              |   2 +
 devel/cbang/Makefile                               |   8 +-
 devel/cbang/files/patch-config_v8_____init____.py  |  21 +++
 .../cbang/files/patch-src_cbang_js_v8_Context.cpp  |  14 ++
 devel/cbang/files/patch-src_cbang_js_v8_V8.h       |   9 ++
 devel/cbang/files/patch-src_cbang_js_v8_Value.cpp  |  20 +++
 devel/cbang/files/patch-src_cbang_js_v8_Value.h    |  11 ++
 devel/cbang/files/patch-src_cbang_net_URI.cpp      |  65 ++++++++
 17 files changed, 482 insertions(+), 11 deletions(-)

diff --git a/cad/camotics/Makefile b/cad/camotics/Makefile
index b6019f6b4329..e42946f7e196 100644
--- a/cad/camotics/Makefile
+++ b/cad/camotics/Makefile
@@ -60,7 +60,7 @@ PORTEXAMPLES=	*
 
 MAKE_ARGS+=	cc="${CC}"	\
 		cxx="${CXX}"	\
-		cxxstd="c++17"	\
+		cxxstd="c++20"	\
 		ccflags="${CCFLAGS}"	\
 		cxxflags="${CXXFLAGS} ${QT5_INC}"	\
 		linkflags="${LDFLAGS} ${QT5_LIBS}"	\
@@ -71,7 +71,8 @@ MAKE_ARGS+=	cc="${CC}"	\
 		compiler="${CHOSEN_COMPILER_TYPE:C/gcc/gnu/}"	\
 		disable_local="zlib bzip2 lz4 sqlite3 expat libevent re2 libyaml"
 MAKE_ENV+=	QT5DIR="${LOCALBASE}"	\
-		CBANG_HOME="${WRKDIR}/cbang"
+		CBANG_HOME="${WRKDIR}/cbang"	\
+		V8_INCLUDE="${LOCALBASE}/include"
 .if defined(WITH_DEBUG)
 MAKE_ARGS+=	debug=1 strict=0
 .endif
diff --git a/databases/postgresql-plv8js/Makefile b/databases/postgresql-plv8js/Makefile
index 89095e267cf6..a308ca5995ce 100644
--- a/databases/postgresql-plv8js/Makefile
+++ b/databases/postgresql-plv8js/Makefile
@@ -14,13 +14,13 @@ LICENSE=	PostgreSQL
 BUILD_DEPENDS=	bash:shells/bash
 LIB_DEPENDS=	libv8.so:lang/v8
 
-USES=		compiler:c++17-lang gmake pgsql
+USES=		compiler:c++20-lang gmake pgsql
 USE_GITHUB=	yes
 PLIST_SUB=	PORTVERSION="${PORTVERSION}"
 
 .include <bsd.port.options.mk>
 
-CXXFLAGS+=	-std=c++17 -fno-rtti
+CXXFLAGS+=	-std=c++20 -fno-rtti -DV8_CPPGC_MICROTASK_QUEUE=1
 # Only for 64-bit architectures
 .if ${ARCH} != armv6 && ${ARCH} != armv7 && ${ARCH} != i386 && ${ARCH} != powerpc && ${ARCH} != powerpcspe
 CXXFLAGS+=	-DV8_COMPRESS_POINTERS=1 -DV8_31BIT_SMIS_ON_64BIT_ARCH=1
diff --git a/databases/postgresql-plv8js/files/patch-Makefile b/databases/postgresql-plv8js/files/patch-Makefile
index 04eaa72f4057..8199929511b5 100644
--- a/databases/postgresql-plv8js/files/patch-Makefile
+++ b/databases/postgresql-plv8js/files/patch-Makefile
@@ -1,6 +1,6 @@
 --- Makefile.orig	2025-07-05 17:44:26 UTC
 +++ Makefile
-@@ -37,20 +37,8 @@ SHLIB_LINK += -Ldeps/v8-cmake/build
+@@ -37,24 +37,12 @@ SHLIB_LINK += -Ldeps/v8-cmake/build
  
  SHLIB_LINK += -Ldeps/v8-cmake/build
  
@@ -22,12 +22,20 @@
  # enable direct jsonb conversion by default
  CCFLAGS += -DJSONB_DIRECT_CONVERSION
  
-@@ -76,7 +64,7 @@ endif
+-CCFLAGS += -Ideps/v8-cmake/v8/include -std=c++17
++CCFLAGS += -Ideps/v8-cmake/v8/include -std=c++20
+ 
+ ifdef EXECUTION_TIMEOUT
+ 	CCFLAGS += -DEXECUTION_TIMEOUT
+@@ -76,9 +64,9 @@ endif
  	REGRESS += bigint_graceful
  endif
  
 -SHLIB_LINK += -lv8_base_without_compiler -lv8_compiler -lv8_snapshot -lv8_inspector -lv8_libplatform -lv8_base_without_compiler -lv8_libsampler -lv8_torque_generated -lv8_libbase
 +SHLIB_LINK += -lv8_libbase -lv8 -lv8_libplatform 
  
- OPTFLAGS = -std=c++17 -fno-rtti -O2
+-OPTFLAGS = -std=c++17 -fno-rtti -O2
++OPTFLAGS = -std=c++20 -fno-rtti -O2
  CCFLAGS += -Wall $(OPTFLAGS)
+ 
+ generate_upgrades:
diff --git a/databases/postgresql-plv8js/files/patch-plv8.cc b/databases/postgresql-plv8js/files/patch-plv8.cc
new file mode 100644
index 000000000000..4b8bf3f61a1b
--- /dev/null
+++ b/databases/postgresql-plv8js/files/patch-plv8.cc
@@ -0,0 +1,60 @@
+--- plv8.cc.orig	2025-07-05 17:44:26 UTC
++++ plv8.cc
+@@ -232,10 +232,10 @@ void PromiseRejectCB(PromiseRejectMessage rejection) {
+ 
+ void PromiseRejectCB(PromiseRejectMessage rejection) {
+ 	auto event = rejection.GetEvent();
+-	if (event == kPromiseRejectAfterResolved || event == kPromiseResolveAfterResolved)
++	if (event == kDeprecatedPromiseRejectAfterResolved || event == kDeprecatedPromiseResolveAfterResolved)
+ 		return;
+ 	auto	promise = rejection.GetPromise();
+-	auto	isolate = promise->GetIsolate();
++	auto	isolate = v8::Isolate::GetCurrent();
+ 
+ 	if (rejection.GetEvent() == v8::kPromiseHandlerAddedAfterReject) {
+ 		if (current_context->ignore_unhandled_promises) return;
+@@ -753,7 +753,7 @@ DoCall(Local<Context> ctx, Handle<Function> fn, Handle
+ DoCall(Local<Context> ctx, Handle<Function> fn, Handle<Object> receiver,
+ 	int nargs, Handle<v8::Value> args[], bool nonatomic)
+ {
+-	Isolate 	   *isolate = ctx->GetIsolate();
++	Isolate 	   *isolate = Isolate::GetCurrent();
+ 	TryCatch		try_catch(isolate);
+ 
+ 	if (isolate->IsExecutionTerminating() || current_context->interrupted) {
+@@ -897,7 +897,7 @@ CallFunction(PG_FUNCTION_ARGS, plv8_exec_env *xenv,
+ 
+ 	Local<Object> recv = Local<Object>::New(xenv->isolate, xenv->recv);
+ 	Local<Function>		fn =
+-		Local<Function>::Cast(recv->GetInternalField(0));
++		Local<Function>::Cast(recv->GetInternalField(0).As<v8::Value>());
+ 	
+ 	Local<v8::Value> result =
+ 		DoCall(context, fn, recv, nargs, args, nonatomic);
+@@ -1014,7 +1014,7 @@ CallSRFunction(PG_FUNCTION_ARGS, plv8_exec_env *xenv,
+ 
+ 	Local<Object> recv = Local<Object>::New(xenv->isolate, xenv->recv);
+ 	Local<Function>		fn =
+-		Local<Function>::Cast(recv->GetInternalField(0));
++		Local<Function>::Cast(recv->GetInternalField(0).As<v8::Value>());
+ 
+ 	Handle<v8::Value> result = DoCall(context, fn, recv, nargs, args, nonatomic);
+ 
+@@ -1148,7 +1148,7 @@ CallTrigger(PG_FUNCTION_ARGS, plv8_exec_env *xenv)
+ 	TryCatch			try_catch(xenv->isolate);
+ 	Local<Object> recv = Local<Object>::New(xenv->isolate, xenv->recv);
+ 	Local<Function>		fn =
+-		Local<Function>::Cast(recv->GetInternalField(0));
++		Local<Function>::Cast(recv->GetInternalField(0).As<v8::Value>());
+ 	Handle<v8::Value> newtup =
+ 		DoCall(context, fn, recv, lengthof(args), args, nonatomic);
+ 
+@@ -1547,7 +1547,7 @@ CompileFunction(
+ 	Local<Context> context = Local<Context>::New(isolate, global_context->context);
+ 	Context::Scope	context_scope(context);
+ 	TryCatch		try_catch(isolate);
+-	v8::ScriptOrigin origin(isolate, name);
++	v8::ScriptOrigin origin(name);
+ 
+ 	// set up the signal handlers
+ 	if (int_handler == NULL) {
diff --git a/databases/postgresql-plv8js/files/patch-plv8.h b/databases/postgresql-plv8js/files/patch-plv8.h
new file mode 100644
index 000000000000..8c3c7edfcb78
--- /dev/null
+++ b/databases/postgresql-plv8js/files/patch-plv8.h
@@ -0,0 +1,44 @@
+--- plv8.h.orig	2026-06-01 05:04:31 UTC
++++ plv8.h
+@@ -217,15 +217,15 @@ class WindowFunctionSupport (public)
+ 		{
+ 			m_plv8obj = v8::Handle<v8::Object>::Cast(
+ 					context->Global()->Get(context, v8::String::NewFromUtf8Literal(
+-						context->GetIsolate(),
++						v8::Isolate::GetCurrent(),
+ 						"plv8",
+ 						v8::NewStringType::kInternalized)).ToLocalChecked());
+ 			if (m_plv8obj.IsEmpty())
+ 				throw js_error("plv8 object not found");
+ 			/* Stash the current item, just in case of nested call */
+-			m_prev_fcinfo = m_plv8obj->GetInternalField(PLV8_INTNL_FCINFO);
++			m_prev_fcinfo = m_plv8obj->GetInternalField(PLV8_INTNL_FCINFO).As<v8::Value>();
+ 			m_plv8obj->SetInternalField(PLV8_INTNL_FCINFO,
+-					v8::External::New(context->GetIsolate(), fcinfo));
++					v8::External::New(v8::Isolate::GetCurrent(), fcinfo, v8::kExternalPointerTypeTagDefault));
+ 		}
+ 	}
+ 	bool IsWindowCall() { return WindowObjectIsValid(m_winobj); }
+@@ -257,17 +257,17 @@ class SRFSupport (public)
+ 	{
+ 	    v8::Local<v8::Value> m_val;
+ 	    if (!context->Global()->Get(context, v8::String::NewFromUtf8Literal(
+-                context->GetIsolate(),
++                v8::Isolate::GetCurrent(),
+                 "plv8",
+                 v8::NewStringType::kInternalized)).ToLocal(&m_val))
+             throw js_error("plv8 object not found");
+ 	    m_plv8obj = v8::Handle<v8::Object>::Cast(m_val);
+-		m_prev_conv = m_plv8obj->GetInternalField(PLV8_INTNL_CONV);
+-		m_prev_tupstore = m_plv8obj->GetInternalField(PLV8_INTNL_TUPSTORE);
++		m_prev_conv = m_plv8obj->GetInternalField(PLV8_INTNL_CONV).As<v8::Value>();
++		m_prev_tupstore = m_plv8obj->GetInternalField(PLV8_INTNL_TUPSTORE).As<v8::Value>();
+ 		m_plv8obj->SetInternalField(PLV8_INTNL_CONV,
+-									v8::External::New(context->GetIsolate(), conv));
++									v8::External::New(v8::Isolate::GetCurrent(), conv, v8::kExternalPointerTypeTagDefault));
+ 		m_plv8obj->SetInternalField(PLV8_INTNL_TUPSTORE,
+-									v8::External::New(context->GetIsolate(), tupstore));
++									v8::External::New(v8::Isolate::GetCurrent(), tupstore, v8::kExternalPointerTypeTagDefault));
+ 	}
+ 	~SRFSupport()
+ 	{
diff --git a/databases/postgresql-plv8js/files/patch-plv8__allocator.cc b/databases/postgresql-plv8js/files/patch-plv8__allocator.cc
new file mode 100644
index 000000000000..da60dc19b02a
--- /dev/null
+++ b/databases/postgresql-plv8js/files/patch-plv8__allocator.cc
@@ -0,0 +1,10 @@
+--- plv8_allocator.cc.orig	2026-06-01 05:04:31 UTC
++++ plv8_allocator.cc
+@@ -67,5 +67,6 @@ void* ArrayAllocator::Reallocate(void *data, size_t ol
+ 			return nullptr;
+ 		}
+ 	}
+-	return this->allocator->Reallocate(data, old_length, new_length);
++	allocated += delta;
++	return std::realloc(data, new_length);
+ }
diff --git a/databases/postgresql-plv8js/files/patch-plv8__allocator.h b/databases/postgresql-plv8js/files/patch-plv8__allocator.h
new file mode 100644
index 000000000000..6bc6e1763e67
--- /dev/null
+++ b/databases/postgresql-plv8js/files/patch-plv8__allocator.h
@@ -0,0 +1,11 @@
+--- plv8_allocator.h.orig	2026-06-01 05:04:31 UTC
++++ plv8_allocator.h
+@@ -22,7 +22,7 @@ class ArrayAllocator : public v8::ArrayBuffer::Allocat
+ 	void* Allocate(size_t length) final;
+ 	void* AllocateUninitialized(size_t length) final;
+ 	void Free(void* data, size_t length) final;
+-	void* Reallocate(void *data, size_t old_length, size_t new_length) final;
++	void* Reallocate(void *data, size_t old_length, size_t new_length);
+ };
+ 
+ #endif //PLV8_PLV8_ALLOCATOR_H
diff --git a/databases/postgresql-plv8js/files/patch-plv8__func.cc b/databases/postgresql-plv8js/files/patch-plv8__func.cc
new file mode 100644
index 000000000000..3c9d9be135ce
--- /dev/null
+++ b/databases/postgresql-plv8js/files/patch-plv8__func.cc
@@ -0,0 +1,175 @@
+--- plv8_func.cc.orig	2026-06-01 05:04:31 UTC
++++ plv8_func.cc
+@@ -71,14 +71,15 @@ WrapCallback(FunctionCallback func)
+ 	Isolate* isolate = Isolate::GetCurrent();
+ 	return External::New(isolate,
+ 			reinterpret_cast<void *>(
+-				reinterpret_cast<uintptr_t>(func)));
++				reinterpret_cast<uintptr_t>(func)),
++			v8::kExternalPointerTypeTagDefault);
+ }
+ 
+ static inline FunctionCallback
+ UnwrapCallback(Handle<v8::Value> value)
+ {
+ 	return reinterpret_cast<FunctionCallback>(
+-			reinterpret_cast<uintptr_t>(External::Cast(*value)->Value()));
++			reinterpret_cast<uintptr_t>(External::Cast(*value)->Value(v8::kExternalPointerTypeTagDefault)));
+ }
+ 
+ static inline void
+@@ -645,8 +646,8 @@ plv8_Prepare(const FunctionCallbackInfo<v8::Value> &ar
+ 	Local<ObjectTemplate> templ = Local<ObjectTemplate>::New(isolate, current_context->plan_template);
+ 
+ 	Local<v8::Object> result = templ->NewInstance(isolate->GetCurrentContext()).ToLocalChecked();
+-	result->SetInternalField(0, External::New(isolate, saved));
+-	result->SetInternalField(1, External::New(isolate, parstate));
++	result->SetInternalField(0, External::New(isolate, saved, v8::kExternalPointerTypeTagDefault));
++	result->SetInternalField(1, External::New(isolate, parstate, v8::kExternalPointerTypeTagDefault));
+ 
+ 	args.GetReturnValue().Set(result);
+ }
+@@ -669,7 +670,7 @@ plv8_PlanCursor(const FunctionCallbackInfo<v8::Value> 
+ 	plv8_param_state   *parstate = NULL;
+ 
+ 	plan = static_cast<SPIPlanPtr>(
+-			Handle<External>::Cast(self->GetInternalField(0))->Value());
++			Handle<External>::Cast(self->GetInternalField(0))->Value(v8::kExternalPointerTypeTagDefault));
+ 
+ 	if (plan == NULL) {
+ 		StringInfoData	buf;
+@@ -694,7 +695,7 @@ plv8_PlanCursor(const FunctionCallbackInfo<v8::Value> 
+ 	 * If the plan has the variable param info, use it.
+ 	 */
+ 	parstate = static_cast<plv8_param_state *>(
+-			Handle<External>::Cast(self->GetInternalField(1))->Value());
++			Handle<External>::Cast(self->GetInternalField(1))->Value(v8::kExternalPointerTypeTagDefault));
+ 
+ 	if (parstate)
+ 		argcount = parstate->numParams;
+@@ -777,7 +778,7 @@ plv8_PlanExecute(const FunctionCallbackInfo<v8::Value>
+ 
+ 
+ 	plan = static_cast<SPIPlanPtr>(
+-			Handle<External>::Cast(self->GetInternalField(0))->Value());
++			Handle<External>::Cast(self->GetInternalField(0))->Value(v8::kExternalPointerTypeTagDefault));
+ 	/* XXX: Add plan validation */
+ 
+ 	if (args.Length() > 0)
+@@ -793,7 +794,7 @@ plv8_PlanExecute(const FunctionCallbackInfo<v8::Value>
+ 	 * If the plan has the variable param info, use it.
+ 	 */
+ 	parstate = static_cast<plv8_param_state *>(
+-			Handle<External>::Cast(self->GetInternalField(1))->Value());
++			Handle<External>::Cast(self->GetInternalField(1))->Value(v8::kExternalPointerTypeTagDefault));
+ 
+ 	if (parstate)
+ 		argcount = parstate->numParams;
+@@ -868,19 +869,19 @@ plv8_PlanFree(const FunctionCallbackInfo<v8::Value> &a
+ 	int					status = 0;
+ 
+ 	plan = static_cast<SPIPlanPtr>(
+-			Handle<External>::Cast(self->GetInternalField(0))->Value());
++			Handle<External>::Cast(self->GetInternalField(0))->Value(v8::kExternalPointerTypeTagDefault));
+ 
+ 	if (plan)
+ 		status = SPI_freeplan(plan);
+ 
+-	self->SetInternalField(0, External::New(isolate, 0));
++	self->SetInternalField(0, External::New(isolate, nullptr, v8::kExternalPointerTypeTagDefault));
+ 
+ 	parstate = static_cast<plv8_param_state *>(
+-			Handle<External>::Cast(self->GetInternalField(1))->Value());
++			Handle<External>::Cast(self->GetInternalField(1))->Value(v8::kExternalPointerTypeTagDefault));
+ 
+ 	if (parstate)
+ 		pfree(parstate);
+-	self->SetInternalField(1, External::New(isolate, 0));
++	self->SetInternalField(1, External::New(isolate, nullptr, v8::kExternalPointerTypeTagDefault));
+ 
+ 	args.GetReturnValue().Set(Int32::New(isolate, status));
+ }
+@@ -899,7 +900,7 @@ plv8_CursorFetch(const FunctionCallbackInfo<v8::Value>
+ 		throw js_error("cannot find cursor");
+ 	}
+ 
+-	CString				cname(self->GetInternalField(0));
++	CString				cname(self->GetInternalField(0).As<v8::Value>());
+ 	Portal				cursor = SPI_cursor_find(cname);
+ 	int					nfetch = 1;
+ 	bool				forward = true, wantarray = false;
+@@ -965,7 +966,7 @@ plv8_CursorMove(const FunctionCallbackInfo<v8::Value>&
+ {
+ 	Isolate*			isolate = args.GetIsolate();
+ 	Handle<v8::Object>	self = args.This();
+-	CString				cname(self->GetInternalField(0));
++	CString				cname(self->GetInternalField(0).As<v8::Value>());
+ 	Portal				cursor = SPI_cursor_find(cname);
+ 	int					nmove = 1;
+ 	bool				forward = true;
+@@ -1007,7 +1008,7 @@ plv8_CursorClose(const FunctionCallbackInfo<v8::Value>
+ plv8_CursorClose(const FunctionCallbackInfo<v8::Value> &args)
+ {
+ 	Handle<v8::Object>	self = args.This();
+-	CString				cname(self->GetInternalField(0));
++	CString				cname(self->GetInternalField(0).As<v8::Value>());
+ 	Portal				cursor = SPI_cursor_find(cname);
+ 
+ 	if (!cursor)
+@@ -1035,17 +1036,17 @@ plv8_ReturnNext(const FunctionCallbackInfo<v8::Value>&
+ plv8_ReturnNext(const FunctionCallbackInfo<v8::Value>& args)
+ {
+ 	Handle<v8::Object>	self = args.This();
+-	Handle<v8::Value>	conv_value = self->GetInternalField(PLV8_INTNL_CONV);
++	Handle<v8::Value>	conv_value = self->GetInternalField(PLV8_INTNL_CONV).As<v8::Value>();
+ 
+ 	if (!conv_value->IsExternal())
+ 		throw js_error("return_next called in context that cannot accept a set");
+ 
+ 	Converter *conv = static_cast<Converter *>(
+-			Handle<External>::Cast(conv_value)->Value());
++			Handle<External>::Cast(conv_value)->Value(v8::kExternalPointerTypeTagDefault));
+ 
+ 	Tuplestorestate *tupstore = static_cast<Tuplestorestate *>(
+ 			Handle<External>::Cast(
+-				self->GetInternalField(PLV8_INTNL_TUPSTORE))->Value());
++				self->GetInternalField(PLV8_INTNL_TUPSTORE).As<v8::Value>())->Value(v8::kExternalPointerTypeTagDefault));
+ 
+ 	conv->ToDatum(args[0], tupstore);
+ 
+@@ -1158,7 +1159,7 @@ plv8_GetWindowObject(const FunctionCallbackInfo<v8::Va
+ 	Isolate*			isolate = args.GetIsolate();
+ 	Handle<v8::Object>	self = args.This();
+ 	Handle<v8::Value>	fcinfo_value =
+-			self->GetInternalField(PLV8_INTNL_FCINFO);
++			self->GetInternalField(PLV8_INTNL_FCINFO).As<v8::Value>();
+ 
+ 	if (!fcinfo_value->IsExternal())
+ 		throw js_error("get_window_object called in wrong context");
+@@ -1179,7 +1180,7 @@ plv8_MyWindowObject(const FunctionCallbackInfo<v8::Val
+ 	Handle<v8::Object>	self = args.This();
+ 	/* fcinfo is embedded in the internal field.  See plv8_GetWindowObject() */
+ 	FunctionCallInfo fcinfo = static_cast<FunctionCallInfo>(
+-			Handle<External>::Cast(self->GetInternalField(0))->Value());
++			Handle<External>::Cast(self->GetInternalField(0))->Value(v8::kExternalPointerTypeTagDefault));
+ 
+ 	if (fcinfo == NULL)
+ 		throw js_error("window function api called with wrong object");
+@@ -1201,7 +1202,7 @@ plv8_MyArgType(const FunctionCallbackInfo<v8::Value>& 
+ {
+ 	Handle<v8::Object>	self = args.This();
+ 	FunctionCallInfo fcinfo = static_cast<FunctionCallInfo>(
+-			Handle<External>::Cast(self->GetInternalField(0))->Value());
++			Handle<External>::Cast(self->GetInternalField(0))->Value(v8::kExternalPointerTypeTagDefault));
+ 
+ 	if (fcinfo == NULL)
+ 		throw js_error("window function api called with wrong object");
+@@ -1636,7 +1637,7 @@ void GetMemoryInfo(v8::Local<v8::Object> obj) {
+ 
+ void GetMemoryInfo(v8::Local<v8::Object> obj) {
+ 	HeapStatistics  	v8_heap_stats;
+-	Isolate 		   *isolate = obj->GetIsolate();
++	Isolate 		   *isolate = Isolate::GetCurrent();
+ 	Handle<Context> context = isolate->GetCurrentContext();
+ 
+ 	isolate->GetHeapStatistics(&v8_heap_stats);
diff --git a/databases/postgresql-plv8js/files/patch-plv8__type.cc b/databases/postgresql-plv8js/files/patch-plv8__type.cc
new file mode 100644
index 000000000000..db8dd5e2d994
--- /dev/null
+++ b/databases/postgresql-plv8js/files/patch-plv8__type.cc
@@ -0,0 +1,20 @@
+--- plv8_type.cc.orig	2026-06-01 05:04:31 UTC
++++ plv8_type.cc
+@@ -575,7 +575,7 @@ CreateExternalArray(void *data, plv8_external_array_ty
+ 	default:
+ 		throw js_error("unexpected array type");
+ 	}
+-	array->SetInternalField(0, External::New(isolate, DatumGetPointer(datum)));
++	array->SetInternalField(0, External::New(isolate, DatumGetPointer(datum), v8::kExternalPointerTypeTagDefault));
+ 
+ 	// needs to be a copy, as the data could go away
+ 	memcpy(buffer->GetBackingStore()->Data(), data, byte_size);
+@@ -593,7 +593,7 @@ ExtractExternalArrayDatum(Handle<v8::Value> value)
+ 	if (value->IsTypedArray())
+ 	{
+ 		Handle<Object> object = Handle<Object>::Cast(value);
+-		return Handle<External>::Cast(object->GetInternalField(0))->Value();
++		return Handle<External>::Cast(object->GetInternalField(0).As<v8::Value>())->Value(v8::kExternalPointerTypeTagDefault);
+ 	}
+ 
+ 	return NULL;
diff --git a/databases/postgresql-plv8js/pkg-plist b/databases/postgresql-plv8js/pkg-plist
index dcd63abeb425..c51f360b84a7 100644
--- a/databases/postgresql-plv8js/pkg-plist
+++ b/databases/postgresql-plv8js/pkg-plist
@@ -41,5 +41,7 @@ share/postgresql/extension/plv8--3.1.7--%%PORTVERSION%%.sql
 share/postgresql/extension/plv8--3.1.8--%%PORTVERSION%%.sql
 share/postgresql/extension/plv8--3.2.0--%%PORTVERSION%%.sql
 share/postgresql/extension/plv8--3.2.1--%%PORTVERSION%%.sql
+share/postgresql/extension/plv8--3.2.2--%%PORTVERSION%%.sql
+share/postgresql/extension/plv8--3.2.3--%%PORTVERSION%%.sql
 share/postgresql/extension/plv8--%%PORTVERSION%%.sql
 share/postgresql/extension/plv8.control
diff --git a/devel/cbang/Makefile b/devel/cbang/Makefile
index 63cdff4d0a96..934c81544ea8 100644
--- a/devel/cbang/Makefile
+++ b/devel/cbang/Makefile
@@ -1,6 +1,6 @@
 PORTNAME=	cbang
 PORTVERSION=	1.8.0
-PORTREVISION=	2
+PORTREVISION=	3
 CATEGORIES=	devel
 
 MAINTAINER=	thierry@FreeBSD.org
@@ -23,10 +23,10 @@ LIB_DEPENDS=	libv8.so:lang/v8		\
 USE_GITHUB=	yes
 GH_ACCOUNT=	CauldronDevelopmentLLC
 
-USES=		compiler:c++17-lang cpe localbase mysql:client python:test scons	\
+USES=		compiler:c++20-lang cpe localbase mysql:client python:test scons	\
 		sqlite ssl
 CPE_VENDOR=	cauldrondevelopment
-USE_CXXSTD=	gnu++17
+USE_CXXSTD=	gnu++20
 USE_LDCONFIG=	yes
 
 OPTIONS_DEFINE=		DEBUG DOCS
@@ -37,7 +37,7 @@ MAKE_ARGS+=	cc="${CC}"	\
 		cxx="${CXX}"	\
 		ccflags="${CCFLAGS}"	\
 		cxxflags="${CXXFLAGS}"	\
-		cxxstd="c++17"	\
+		cxxstd="c++20"	\
 		libpath="/lib"	\
 		sharedlib=1	\
 		prefix="${STAGEDIR}${PREFIX}"	\
diff --git a/devel/cbang/files/patch-config_v8_____init____.py b/devel/cbang/files/patch-config_v8_____init____.py
new file mode 100644
index 000000000000..25afb89f5467
--- /dev/null
+++ b/devel/cbang/files/patch-config_v8_____init____.py
@@ -0,0 +1,21 @@
+--- config/v8/__init__.py.orig	2023-03-09 16:36:47 UTC
++++ config/v8/__init__.py
+@@ -8,13 +8,17 @@ def configure(conf):
+     else: lib_suffix.append('/build/Debug/lib')
+ 
+     if not 'V8_INCLUDE' in os.environ and not 'V8_HOME' in os.environ:
+-        os.environ['V8_INCLUDE'] = '/usr/include/v8'
++        os.environ['V8_INCLUDE'] = '/usr/local/include'
+ 
+     conf.CBCheckHome('v8', lib_suffix = lib_suffix)
+ 
+     if conf.env['PLATFORM'] == 'win32' or int(conf.env.get('cross_mingw', 0)):
+         conf.CBRequireLib('winmm')
+         conf.CBCheckLib('dbghelp')
++
++    # V8 >= 15.4 refuses to compile unless the embedder defines this
++    conf.env.AppendUnique(CPPDEFINES = ['V8_CPPGC_MICROTASK_QUEUE=1'])
++    conf.env.CBConfigDef('V8_CPPGC_MICROTASK_QUEUE=1')
+ 
+     conf.CBRequireCXXHeader('v8.h')
+     conf.CBRequireCXXHeader('libplatform/libplatform.h')
diff --git a/devel/cbang/files/patch-src_cbang_js_v8_Context.cpp b/devel/cbang/files/patch-src_cbang_js_v8_Context.cpp
new file mode 100644
index 000000000000..b6d09c885038
--- /dev/null
+++ b/devel/cbang/files/patch-src_cbang_js_v8_Context.cpp
@@ -0,0 +1,14 @@
+--- src/cbang/js/v8/Context.cpp.orig	2026-06-01 05:31:12 UTC
++++ src/cbang/js/v8/Context.cpp
+@@ -51,11 +51,7 @@ Value Context::eval(const InputSource &src) {
+   string filename = src.getName();
+   if (!filename.empty()) origin = Value::createString(filename);
+ 
+-#if V8_MAJOR_VERSION < 10
+   v8::ScriptOrigin sOrigin(origin);
+-#else
+-  v8::ScriptOrigin sOrigin(Value::getIso(), origin);
+-#endif
+ 
+   // Get script source
+   auto source = Value::createString(src.toString());
diff --git a/devel/cbang/files/patch-src_cbang_js_v8_V8.h b/devel/cbang/files/patch-src_cbang_js_v8_V8.h
new file mode 100644
index 000000000000..28f383eb8dba
--- /dev/null
+++ b/devel/cbang/files/patch-src_cbang_js_v8_V8.h
@@ -0,0 +1,9 @@
+--- src/cbang/js/v8/V8.h.orig	2026-06-01 05:31:12 UTC
++++ src/cbang/js/v8/V8.h
+@@ -40,6 +40,4 @@
+ #include <stdio.h>
+ #define V8STDINT_H_
+ 
+-#define V8_ENABLE_CHECKS
+-
+ #include <v8.h>
diff --git a/devel/cbang/files/patch-src_cbang_js_v8_Value.cpp b/devel/cbang/files/patch-src_cbang_js_v8_Value.cpp
new file mode 100644
index 000000000000..0a17ce81bf8a
--- /dev/null
+++ b/devel/cbang/files/patch-src_cbang_js_v8_Value.cpp
@@ -0,0 +1,20 @@
+--- src/cbang/js/v8/Value.cpp.orig	2026-06-01 05:31:12 UTC
++++ src/cbang/js/v8/Value.cpp
+@@ -47,7 +47,7 @@ namespace {
+ namespace {
+   void _callback(const v8::FunctionCallbackInfo<v8::Value> &info) {
+     js::Callback &cb =
+-      *static_cast<js::Callback *>(v8::External::Cast(*info.Data())->Value());
++      *static_cast<js::Callback *>(v8::External::Cast(*info.Data())->Value(v8::kExternalPointerTypeTagDefault));
+ 
+     try {
+       // Convert args
+@@ -87,7 +87,7 @@ Value::Value(const js::Function &func) {
+   JSImpl::current().add(cb);
+ 
+   v8::Handle<v8::Value> data =
+-    v8::External::New(getIso(), (void *)cb.get());
++    v8::External::New(getIso(), (void *)cb.get(), v8::kExternalPointerTypeTagDefault);
+   v8::Handle<v8::FunctionTemplate> tmpl =
+     v8::FunctionTemplate::New(getIso(), &_callback, data);
+   value = tmpl->GetFunction(getCtx()).ToLocalChecked();
diff --git a/devel/cbang/files/patch-src_cbang_js_v8_Value.h b/devel/cbang/files/patch-src_cbang_js_v8_Value.h
new file mode 100644
index 000000000000..ad11c8bdcf95
--- /dev/null
+++ b/devel/cbang/files/patch-src_cbang_js_v8_Value.h
@@ -0,0 +1,11 @@
+--- src/cbang/js/v8/Value.h.orig	2026-06-01 05:31:12 UTC
++++ src/cbang/js/v8/Value.h
+@@ -135,7 +135,7 @@ namespace cb {
+       std::string toString() const;
+ 
+       int utf8Length() const {
+-        return v8::String::Cast(*value)->Utf8Length(getIso());
++        return v8::String::Cast(*value)->Utf8LengthV2(getIso());
+       }
+ 
+       // Object
diff --git a/devel/cbang/files/patch-src_cbang_net_URI.cpp b/devel/cbang/files/patch-src_cbang_net_URI.cpp
new file mode 100644
index 000000000000..f127525b5b1f
--- /dev/null
+++ b/devel/cbang/files/patch-src_cbang_net_URI.cpp
@@ -0,0 +1,65 @@
+--- src/cbang/net/URI.cpp.orig	2026-06-01 04:53:38 UTC
++++ src/cbang/net/URI.cpp
+@@ -282,7 +282,7 @@ string URI::encode(const string &s, const char *unesca
+   string result;
+ 
+   for (unsigned i = 0; i < s.length(); i++)
+-    if (contains(unescaped, s[i])) result.append(1, s[i]);
++    if (::contains(unescaped, s[i])) result.append(1, s[i]);
+     else result.append(String::printf("%%%02x", (unsigned)s[i]));
+ 
+   return result;
+@@ -331,7 +331,7 @@ void URI::parsePathSegment(const char *&s) {
+   string seg;
+ 
+   while (true)
+-    if (contains(PATH_SEGMENT_CHARS, *s)) seg.append(1, *s++);
++    if (::contains(PATH_SEGMENT_CHARS, *s)) seg.append(1, *s++);
+     else if (*s == '%') seg.append(1, parseEscape(s));
+     else break;
+ 
+@@ -343,7 +343,7 @@ void URI::parseScheme(const char *&s) {
+   if (!isalpha(*s)) THROW("Expected alpha at start of scheme");
+ 
+   while (true)
+-    if (contains(SCHEME_CHARS, *s)) scheme.append(1, *s++);
++    if (::contains(SCHEME_CHARS, *s)) scheme.append(1, *s++);
+     else break;
+ 
+   match(s, ':');
+@@ -378,7 +378,7 @@ string URI::parseUserPass(const char *&s) {
+   string result;
+ 
+   while (true)
+-    if (contains(USER_PASS_CHARS, *s)) result.append(1, *s++);
++    if (::contains(USER_PASS_CHARS, *s)) result.append(1, *s++);
+     else if (*s == '%') result.append(1, parseEscape(s));
+     else break;
+ 
+@@ -394,7 +394,7 @@ void URI::parseHost(const char *&s) {
+ 
+ void URI::parseHost(const char *&s) {
+   while (true)
+-    if (contains(HOST_CHARS, *s)) host.append(1, *s++);
++    if (::contains(HOST_CHARS, *s)) host.append(1, *s++);
+     else break;
+ 
+   if (host.empty()) THROW("Expected host character");
+@@ -429,7 +429,7 @@ string URI::parseName(const char *&s) {
+   string result;
+ 
+   while (true)
+-    if (contains(NAME_CHARS, *s)) result.append(1, *s++);
++    if (::contains(NAME_CHARS, *s)) result.append(1, *s++);
+     else if (*s == '%') result.append(1, parseEscape(s));
+     else break;
+ 
+@@ -443,7 +443,7 @@ string URI::parseValue(const char *&s) {
+   string result;
+ 
+   while (true)
+-    if (contains(VALUE_CHARS, *s)) result.append(1, *s++);
++    if (::contains(VALUE_CHARS, *s)) result.append(1, *s++);
+     else if (*s == '%') result.append(1, parseEscape(s));
+     else break;
+