You are not logged in.
Round 2 done - extensive matrix green on real aarch64, one small inconsistency to flag.
Platform: Rockchip RK3588 (aarch64-linux, FPC 3.2.3), branch body-input-stream @ 9d238e691.
Standalone harness on THttpServerGeneric.OnBodyDownload spooling to a TFileStreamEx, run for
BOTH THttpServer and THttpAsyncServer, driven by real curl over loopback, with real disk-full
(spool dir on a filled tmpfs), SHA-256 byte-integrity checks, and multi-GB uploads on a board
with enough storage.
All good:
- Byte integrity (SHA-256, spooled file == uploaded content): Content-Length, chunked,
rate-limited, and a 2.5 GB upload - all identical, on both families.
- Normal / in-memory fallback (event returns nil) / 4x concurrent / slow link /
chunked (no Content-Length): 200.
- Disk-full while spooling: 507 on both families, for both Content-Length and chunked bodies.
- 6 concurrent uploads into a full disk: every one gets 507, none wrongly succeeds, the server
stays up and serves normally again once space is freed.
- Content-Encoding: gzip body: cleanly rejected with 415.
- MaximumAllowedContentLength: a Content-Length body over the limit -> 413 on both families;
an in-limit body -> 200.
- Int64 / large files: a 4.5 GB upload (> 2^32) is spooled fully on both families -
aContentLength and the final spooled size are both exactly 4831838208 (no 32-bit truncation);
a 2.5 GB upload (> 2^31) is byte-identical (SHA-256) to the source.
- Keep-alive: two different-sized uploads on one reused connection both succeed (per-request
state reset is correct).
- Cleanup: THttpServer deletes the spool immediately; THttpAsyncServer defers to the connection
GC (~12 s). No spool file leak.
One inconsistency worth a look:
- For a chunked body (no Content-Length) that exceeds MaximumAllowedContentLength,
THttpAsyncServer answers a clean "400 Bad Request" and closes, but THttpServer just resets the
connection with NO HTTP status (curl reports 000). Both are safe - neither invokes OnRequest,
neither processes the oversized body, and the partial spool is reclaimed - but the sync server
gives the client no way to tell "too large" from a network error. A 413 (as the Content-Length
path already returns) before closing would make the two families consistent.
Minor: for a chunked body, aContentLength reaches OnBodyDownload as -1 on the async server but 0
on the sync server.
Everything else - including the 9d238e691 async 507 restriction - works well on aarch64.
This is exactly what the open-source community is all about! Congrats on mastering mORMot 2 and Free Pascal. Your persistence and willingness to learn are a great example for all of us. Thanks for being part of this journey! ❤️
Implemented, PR is up: https://github.com/synopse/mORMot2/pull/510
resourcestrings. On FPC the only official hook is the objpas.SetResourceStrings() callback over the per-unit writable table (the enumeration-style API is gone in 3.x); both gettext.pp (.mo) and LazUtils translations.pas (.po) go through it. Delphi has no such table, so this channel is FPC-only - Mustache and captions stay the common ground.
Formats. .po as the main one, msgid = the English text. .ini (and .msg) supported too, and .yaml / .json came along nearly for free from our own parsers. msgctxt and plurals out of scope.
TLanguage. Reused, not duplicated - the registry is indexed by it, and ISO lookup goes through IsoTextToLanguage().
Date/time. The per-language pattern now goes through our own TFormatSettings: on a POSIX-locale box the RTL was rewriting '/' into '-', which silently defeated the whole point of having a per-language pattern.
Unit placement. I kept mormot.core.i18n rather than mormot.ui: everything in src/ui pulls LCL/VCL and lives in the separate mormot2ui package, while our own case is a headless Linux server rendering Mustache views - it would have to depend on the ui package just to translate them. No form translation is planned, so there is nothing UI-specific in there. Happy to move it if you disagree.
Green on FPC 3.2.3 i386-win32, FPC 3.2.3 aarch64-linux (RK3588 hardware) and Delphi 37.0 Win32 - 192 assertions on FPC, 183 on Delphi (the difference being the resourcestring part).
@flydev - this should cover your mvc-blog case. Two things you will like: TMvcViewsMustache.OnTranslate (PR #506, already merged) means your TMVCViewsMustacheI18n subclass can go away entirely, and the "ugly to create and free a changed language on render" part you mentioned is gone too - the tables stay loaded, and each request just calls SetThreadLanguage(), so nothing is allocated during rendering.
Two caveats for you, though. Your .msg files do parse fine as INI, but nothing will match: your keys are Hash32(WinAnsi(English)) while ours are the English text itself. Joining XX.msg with Project1.messages on the hash gives you a ready .po - happy to write that converter if it helps. Note that your | and ~ line-break encoding is your own convention, which our INI loader does not know about, so entries with line breaks should go through .po (which has native multi-line support). And on Delphi, resourcestrings are not covered by us - keep your RedirectCode approach for those.
If a TimeFormat, or reading the date patterns from the translation file itself, matters to you, say so and I will add it.
Ran the body-input-stream branch on real hardware, as promised: Rockchip RK3588 (aarch64-linux, FPC 3.2.3), THttpAsyncServer with OnBodyDownload spooling to a temp file (TFileStreamEx + TemporaryFileName), the handler renaming the spool file to take ownership. Client: curl from a LAN Windows box, payload = our real 111 MB firmware upgrade package.
scenario result
--------------------------------------------- ---------------------------------------------------
single 111 MB upload OK - MD5 matches end to end, 3.2 s on gigabit LAN
4 concurrent (1x 111 MB + 3x 20 MB) OK - all MD5s match, no temp-file collisions,
no leftovers
slow link (--limit-rate 1M, 20 MB) OK - MD5 matches
client killed mid-transfer (~30 of 111 MB) OK - the half-written spool file was removed by
the server within ~7-22 s after the disconnect,
nothing ever reached the target folder, and the
server kept serving
disk full (spool dir on a 40 MB tmpfs via spool space fully reclaimed within seconds, the
TMPDIR, uploading 111 MB) server stayed alive and functional - but the
client got a raw connection reset (curl exit 56,
no HTTP status), see belowTwo observations:
- on ENOSPC the connection is simply reset, so the client gets no HTTP response at all. Would it be worth sending e.g. 507 Insufficient Storage (or plain 500) before closing, when writing to the destination stream fails? For an upgrade-package UI the distinction between "server out of disk" and "network broke" is quite valuable.
- the spool cleanup after a hard client disconnect is not immediate (measured between 7 and 22 s later) - perfectly fine for us, just documenting the observed behavior.
Nice work - this fits our use case exactly. TMPDIR being honored by TemporaryFileName() also made the disk-full isolation easy.
Thanks ab — happy to share a bit more.
The boxes are Rockchip RK3588 / RK3576 / RV1126B (aarch64 Linux, 2-4 GB RAM) doing real-time video transcoding with the SoC hardware codecs, dozens of channels per box. The transcoding API server is built on mORMot 2: THttpAsyncServer for the REST API, TSynLog for logging, TDocVariant/JSON everywhere. Static-linked dependency-free binaries are a perfect match for embedded rootfs images.
The next step — and the reason behind our recent i18n and XML activity — is migrating the companion web admin (currently Java + Tomcat) to mORMot MVC on the same boxes, which will remove the whole JRE from the firmware. That is also where the 100+ MB upgrade-package upload use case comes from.
Progress update: the first-stage PR is submitted — PR #506, https://github.com/synopse/mORMot2/pull/506 (TMvcViewsMustache.OnTranslate, wiring the built-in {{"text}} channel for MVC apps, with test).
Once it is merged, we will proceed as planned with the second-stage PR — the mormot.core.i18n unit itself, following the sketch and the three open questions from the opening post.
Thanks flydev — very useful links, I had missed both threads.
That is encouraging: ab already said twice that an official i18n unit is wanted, and your mormot.i18n is a faithful port of the v1 unit — we will definitely use it as a reference for the v1 semantics. The piece we are aiming at here is complementary: a cross-platform core unit (no VCL/Windows dependency, POSIX included), so your VCL scenario and our embedded Linux MVC scenario could share the same table/loader layer.
One more thing I found meanwhile: the framework already has the TLanguage enum with ISO/LCID tables in mormot.core.unicode, so PR-B would naturally key languages on TLanguage instead of introducing a new identity type.
Happy to have you test/review once there is a branch.
Hi ab,
mORMot 2 currently has no i18n unit, but the framework clearly kept the slots for one when migrating from v1's mORMoti18n.pas:
- TOnStringTranslate / TOnUtf8Translate callback types in mormot.core.unicode (the comments even reference TLanguageFile.Translate from v1);
- the LoadResStringTranslate global hook for resourcestring translation;
- i18nDateText / i18nDateTimeText hooks in mormot.core.datetime (already consumed by the Mustache DateTimeToText helper);
- the Mustache {{"text}} built-in translate tag, and the OnTranslate parameter of every TSynMustache.Render* overload.
None of them is wired anywhere, and the MVC layer cannot even reach the Mustache channel: TMvcViewsMustache.Render calls TSynMustache.Render without passing OnTranslate, and exposes no property for it.
Our concrete need: we are migrating a Java + Tomcat web admin application to mORMot 2 MVC (embedded aarch64 Linux boxes), with a bilingual zh/en UI — about 400 translation keys across ~46 Mustache templates, language selected per request (URL parameter + cookie). So this is driven by a real production application, and we would contribute it in two steps:
PR-A (tiny, self-contained — we will submit it shortly): add an OnTranslate: TOnStringTranslate property to TMvcViewsMustache and pass it down in Render. About a dozen lines plus a test. This just connects the existing {{"text}} channel for MVC apps; language negotiation stays in the application (the callback has no request context, which we believe is the right minimal contract).
PR-B (the actual unit — API to be agreed here first): mormot.core.i18n with roughly:
- TSynLanguage / TSynLanguages: one key->text table per language + a registry;
- loaders: JSON first (TDocVariant-friendly), optionally the v1 .msg format for backward compatibility;
- SetThreadLanguage / ThreadLanguage (threadvar) so web apps can set the language per request;
- one-call wiring: hook LoadResStringTranslate, i18nDateText/i18nDateTimeText, and return a TOnStringTranslate suitable for Mustache / MVC;
- non-goals: VCL/LCL form translation (v1's big part, no UI layer in v2 core), plural rules / ICU MessageFormat (could be an extension point later).
Questions before we write code:
1. Unit placement: a new mormot.core.i18n, or would you rather extend an existing unit? (new-unit additions are your call)
2. Any preference on the language table shape (TSynNameValue vs TDynArrayHashed) and on the loader formats?
3. Is .msg (v1) compatibility worth carrying into v2, or JSON-only?
We would follow the same process as the YAML support (#466): agree on the API here, then submit with full tests (our real 400-key zh/en table as fixture, missing-key fallback, thread isolation, resourcestring hook) on FPC + Delphi.
Just adding a real-world use case in support of this plan.
We are migrating a Java + Tomcat web admin application to mORMot 2, running on embedded aarch64 Linux boxes with 2 GB of RAM. The single biggest HTTP payload there is the firmware upgrade package upload: 100+ MB in one POST. Buffering such a body in memory is not an option on these devices, so a callback that lets the server hand the incoming body to a TStream (with the spooled temp file as safe default) once headers are parsed is exactly what we need — +1 to all seven points from our side.
One emphasis: coverage of the async server family (the "safe for the async family" design in point 2) is the part that matters most to us, since THttpAsyncServer is where we want to end up.
And an offer: once there is a branch to test, we can run it on real aarch64 hardware with FPC — real upgrade packages, slow links, interrupted uploads, concurrent uploads, disk-full — and report the results back here.
Congrats to Arnaud! Well earned this award!
Hey! I rarely use /bmad-create-story. Since my project requirements are clear and not overly complex, I usually run /bmad-quick-dev directly. It covers spec drafting, development, review and acceptance testing in one flow. You can also start with /bmad-create-prd, BMAD will guide you step by step.Impressive 10-hour run! Totally agree BMAD outperforms Cavekit Blueprints.
Wow, these 10 practical skills are truly excellent and highly useful. They can effectively make AI-generated programs more consistent and compliant with the mORMot-Style specifications! I sincerely appreciate the hard work and outstanding development efforts from flydev. When it comes to the overall process framework design and construction, I personally highly recommend the BMAD Method. That’s right, I have fully migrated from superpower to this framework long ago. Although its overall structure is more complex, it is completely worthwhile once you become proficient in using it.
Thanks a lot @flydev! Really appreciate you sharing this mormot2 MCP implementation and the extensions repo — it looks great!
Just to give you a heads-up: I’m currently buried deep in a production project,so I probably won’t have time to test it properly until after the end of March, unless the project wraps up early unexpectedly.
Once I’m free, I’ll definitely give it a spin and provide feedback! ?
Thank you Chaa, but TSynFPUException.ForLibraryCode cannot solve this issue.
Our program structure is:
// Main thread (FPC)
TS_Init(apiKey, callback); // Initialize SDK (libvct.so)
TS_createTask(taskConfig); // Create transcoding task
// Main thread now waits for callback...
while not Terminated do
Sleep(10);The SDK (libvct.so) is a C library that internally uses GStreamer and FFmpeg. When we call TS_createTask(), the function returns immediately. The actual transcoding happens asynchronously inside libvct.so's internal threads.
The crash occurs like this:
Main Thread (FPC): SDK Internal Threads:
TS_Init()
TS_createTask() ------> libvct.so creates worker threads
waiting... |
waiting... v
waiting... GStreamer/FFmpeg processing
waiting... |
waiting... v
Thread "queue210:src" calls pow()
-> resolves to mORMot's p0w()
-> FPC ThreadVar not initialized
-> CRASH
We cannot wrap our SDK calls with TSynFPUException.ForLibraryCode:
with TSynFPUException.ForLibraryCode do begin
TS_createTask(taskConfig); // Returns immediately!
end;
// ForLibraryCode scope ends here, but crash happens
// much later in a different thread we don't controlThe ForLibraryCode pattern only works when the C code executes synchronously in the calling thread. It cannot help when the C library spawns its own threads that later call pow().
This is why we need a fix in mORMot itself - either a define like -dNOLIBCMATH or making the exported functions safe for uninitialized threads.
Environment
- Platform: aarch64-linux (ARM64, Rockchip RK3588)
- FPC: 3.2.2
- mORMot2: latest
- Application: FPC program linked with GStreamer + FFmpeg (libavcodec)
Symptom
Program crashes with EInvalidOp: Invalid floating point operation when FFmpeg's libavcodec calls pow() during audio encoder initialization.
GDB Backtrace
Thread 28 "queue210:src" hit Breakpoint 1, SYSTEM_$_FLOAT_RAISE$TFPUEXCEPTIONMASK ()
(gdb) bt
#0 SYSTEM_$_FLOAT_RAISE$TFPUEXCEPTIONMASK ()
#1 SYSTEM_$_FLOAT_RAISE$TFPUEXCEPTION ()
#2 SYSTEM_$_RAISEPENDINGEXCEPTIONS ()
#3 SYSTEM_$_FPC_THROWFPUEXCEPTION ()
#4 fpc_ln_real ()
#5 MATH_$_POWER$DOUBLE$DOUBLE$DOUBLE ()
#6 p0w (b=-18014398509481984, e=0) at mormot.lib.static.pas:1014
#7 () at /lib/aarch64-linux-gnu/libavcodec.so.58
...
#10 avcodec_open2 () at /lib/aarch64-linux-gnu/libavcodec.so.58Root Cause Analysis
The issue involves three interacting factors:
1. Symbol Export in mormot.lib.static.pas
mORMot exports C-compatible math functions with standard libc symbol names:
function p0w(b, e: double): double; cdecl; export alias: 'pow';
begin
result := Power(b, e); // calls FPC's Math.Power
end;When the FPC program is linked, these symbols override the libc versions. Any C library (like libavcodec) calling pow() will resolve to mORMot's p0w instead of glibc's implementation.
2. FPC's Soft-Float Exception Handling on aarch64
On aarch64, FPC uses software-based floating-point exception detection. The FPU exception state is stored in a ThreadVar:
// From FPC RTL system unit
threadvar
softfloat_exception_flags: TFPUExceptionMask;
softfloat_exception_mask: TFPUExceptionMask;After each floating-point operation, FPC checks softfloat_exception_flags and raises Pascal exceptions if needed.
3. ThreadVar Initialization in Foreign Threads
The critical issue: ThreadVars are only initialized for threads created by FPC's BeginThread.
When a C library (GStreamer/libavcodec) creates its own threads using pthread_create, FPC's ThreadVar storage is NOT initialized. The softfloat_exception_mask contains garbage/zero instead of the default mask that suppresses common exceptions.
Call Flow
1. GStreamer creates worker thread via pthread_create
-> FPC ThreadVars NOT initialized (softfloat_exception_mask = 0 or garbage)
2. libavcodec calls pow() for audio encoding setup
-> Resolves to mORMot's p0w() due to symbol export
3. p0w() calls FPC's Power() -> calls fpc_ln_real()
-> FPC soft-float code checks softfloat_exception_flags
-> Uninitialized mask causes false positive exception detection
4. FPC raises EInvalidOp in a non-FPC thread
-> CrashCurrent Workaround
Define NOLIBCSTATIC to disable all C library function exports:
-dNOLIBCSTATICThis works but disables ALL static C functions, which may be too aggressive.
Suggested Improvement
Rather than disabling all exports, consider a more targeted approach:
Option A: Separate define for math function exports
{$ifndef NOLIBCMATH}
function p0w(b, e: double): double; cdecl; export alias: 'pow';
function fl00r(x: double): double; cdecl; export alias: 'floor';
function ce1l(x: double): double; cdecl; export alias: 'ceil';
// ...
{$endif}Option B: Make these functions thread-safe for foreign threads
Check if running in a properly initialized FPC thread before using FPC math:
function p0w(b, e: double): double; cdecl; export alias: 'pow';
begin
// If ThreadVar not initialized, fall back to libc or use safe implementation
if not IsFPCThreadInitialized then
result := libc_pow(b, e) // direct syscall or dlsym
else
result := Power(b, e);
end;Option C: Document the limitation
At minimum, document that programs using C libraries that create threads (GStreamer, FFmpeg, SDL, etc.) should define NOLIBCSTATIC on aarch64.
docs: update JSON wiki for mORMot 2.4 delimiter syntax support
- Update version info from 2.3 to 2.4 (version 2.4.13376) in all 15 chapters
- Add "Creating Nested Structures" section in Chapter 3 documenting:
- Delimiter syntax: '{' '}' '[' ']' in DocDict/DocList
- .AsVariant approach for nested structures
- Direct IDocDict/IDocList parameters
- Add FAQ Q17 about delimiter syntax support in DocDict/DocList
- Document feature parity between JsonEncode() and DocDict/DocList
Access: https://github.com/zen010101/Claude_Mor … sing-Guide
Haha, yeah, maybe AI hallucinations offer a new way for humans to innovate. ![]()
I did see that the recent commits already have these features covered. I've just been swamped with other stuff recently and haven't gotten around to updating this Wiki doc.
Thank you ab for the valuable feedback! I have updated the JSON wiki documentation accordingly:
1. Delimiter Syntax Clarification
Fixed the documentation to clarify that `'{' '}' '[' ']'` delimiter syntax is supported by `JsonEncode()` only, NOT by `DocDict()`/`DocList()`. All examples now correctly use `.AsVariant` for nesting:
// CORRECT for DocDict/DocList
dict := DocDict([
'user', DocDict([
'name', 'Alice',
'roles', DocList(['admin', 'user']).AsVariant
]).AsVariant
]);2. SOA Service Best Practices
Added documentation recommending RawJson + JsonEncode() for SOA interface-based services instead of IDocDict/IDocList:
- New section in Chapter 3: "SOA Service Considerations"
- New FAQ Q16: "Should I use IDocDict/IDocList in SOA service methods?"
- Notes added to API response examples
3. FPC Stable RTTI Registration
- Rtti.RegisterType() is NOT enough for FPC stable or older Delphi
- Must use Rtti.RegisterFromText() with manual field description
Thanks again for the corrections!
Thank you very much, Arnaud! Your detailed review is invaluable.
I've addressed all the issues in the latest update:
1. Record variable name - Fixed the reserved word issue
2. O(n) complexity - Corrected to O(log n) for sorted IDocDict lookup
3. Log format - Changed recommendation from jsonHumanReadable to jsonUnquotedPropNameCompact (grep-friendly, single line per entry)
4. RTTI clarification - Added note that Rtti.RegisterType is mainly needed for FPC stable versions; Delphi 2010+ has enhanced RTTI for basic serialization
5. Get() methods - Rewrote section to show all 11 boolean-returning overloads with proper usage patterns
6. Performance chapter - Deleted due to measurement errors (now 14 chapters total)
7. Native nested syntax - Added documentation for the '{' '}' '[' ']' delimiter syntax
The wiki has been updated and chapters renumbered accordingly.
Hi all,
Following the RawUtf8 guide, I've created a comprehensive guide for mORMot 2's JSON processing interfaces - IDocList, IDocDict, and IDocDicts.
Wiki Link: https://github.com/zen010101/Claude_Mor … sing-Guide
The guide is organized into 15 chapters covering:
- Core Concepts - What are IDocList/IDocDict, design philosophy, relationship with TDocVariant
- Type System - Interface hierarchy, IDocDicts array type, TDocVariantModel options
- Creating Instances - Factory functions (DocList, DocDict, DocListFromResults, DocDictDynArray)
- IDocList Guide - Element access, iteration, filtering, sorting
- IDocDict Guide - Key access, path-based navigation (PathDelim), iteration
- IDocDicts Guide - Working with arrays of IDocDict
- ORM Integration - jsonfromresults parameter, expanded vs non-expanded formats
- Serialization - JSON output formats (compact, human-readable)
- Error Handling - Parse failure behaviors, validation patterns
- Performance - Typed accessors, Sort for O(log n) lookup, model selection
- Memory Management - Weak references vs deep copies, Objects iterator pitfalls
- API Reference - Complete method signatures
- Practical Examples - Real-world usage patterns
- FAQ - Common questions and solutions
All code examples have been validated through unit tests (455 assertions passed).
Hope this helps those working with JSON in mORMot 2. Feedback and corrections are welcome!
Good idea, the wiki can also be managed by multiple people collaboratively using git. However, we can wait until I generate more articles later and then move them over uniformly.
Another example of Rust's invasion ;-)
Use ChangeSqlEncryptTablePassWord() function from mormot.db.raw.sqlite3.static unit:
ChangeSqlEncryptTablePassWord('mydata.db3', 'OldPassword', 'NewPassword');
Notes:
- Database file must be closed before calling this function
- To check if a file is encrypted: IsSQLite3FileEncrypted('mydata.db3')
Don't forget to back up your database before the test!!!
Wow, there are so many demo programs! It feels like their number has even exceeded the examples that come with mORMot itself! I don't think anyone would dislike more examples, heh heh.
Personally, I still prefer the ThirdParty solution. Just like it is now, it can greatly reduce everyone's cost of obtaining examples.
In mORMot 2, SQLite3 encryption is built into the static library. Here's how to use it:
1. Download static files from https://synopse.info/files/mormot2static.7z
2. Add mormot.db.raw.sqlite3.static to your uses clause
3. Pass password when creating the database:
uses
mormot.db.raw.sqlite3.static,
mormot.orm.sqlite3;
var
Model: TOrmModel;
Rest: TRestServerDB;
begin
Model := TOrmModel.Create([...]);
Rest := TRestServerDB.Create(Model, 'mydata.db3', False, 'MyPassword');
Or with TSqlDatabase directly:
DB := TSqlDatabase.Create('mydata.db3', 'MyPassword');
Important notes:
- This encryption is NOT compatible with official SQLite SEE or wxsqlite3
- NOT compatible with old mORMot 1 format (before 1.18.4413) - use OldSqlEncryptTablePassWordToPlain() to migrate
- Uses AES-128 with PBKDF2 key derivation
These questions are more about your application's business logic and architecture design rather than mORMot framework usage itself.
For general code review and architecture advice, you might consider:
- Stack Overflow / Code Review Stack Exchange
- Hiring a consultant
- Reading DDD/Clean Architecture books
The mORMot forum is best suited for questions about how to use the framework's APIs and features.
That said, regarding ComputeFieldsBeforeWrite specifically: the mORMot documentation shows it's intended for lightweight field computation (timestamps, derived values). Querying the database inside it is not recommended.
I see your ComputeFieldsBeforeWrite implementation:
NextID := aRest.TableMaxID(TOrmUser) + 1;
if (aOccasion = oeAdd) and ((FInviteCode = '') or (NextID <= 3)) then
FInviteCode := GenerateUniqueInviteCode(NextID);
Issues:
1. Race condition — TableMaxID + 1 is unreliable under concurrent inserts. Two users registering simultaneously may get the same NextID, causing duplicate invite codes.
2. Database query in ComputeFieldsBeforeWrite — This method is called during the write operation. Querying TableMaxID here adds latency and potential deadlocks.
Recommended fix:
Generate the invite code after the record is inserted, when you have the actual ID:
// In your registration service:
function TDomainUserManager.RegisterUser(...): TID;
begin
Result := fOrm.Add(aUser, True); // Insert first, get real ID
if Result > 0 then
begin
aUser.IDValue := Result;
aUser.InviteCode := GenerateUniqueInviteCode(Result);
fOrm.Update(aUser, 'InviteCode'); // Update only this field
end;
end;
Or use a UUID-based invite code that doesn't depend on ID:
FInviteCode := Int64ToHex(Random64) + Int64ToHex(Random64); // 32-char hex
Also, what's the purpose of NextID <= 3? If it's for seeding admin accounts, consider handling that separately in InitializeTable.
Hi Arnaud,
While migrating the mORMot 1 examples (specifically Example 22 "JavaScript HTTPApi web server" and Example 23 "JavaScript Tests") to mORMot 2, I noticed that although mORMot 2 has integrated QuickJS via mormot.lib.quickjs, it lacks a high-level wrapper similar to what SynSM (SpiderMonkey) provided in mORMot 1.
I've implemented TQuickJSEngine in mormot.script.quickjs.pas to fill this gap. The main features include:
TQuickJSEngine - Thread-safe JavaScript engine (inherits from TThreadSafeEngine):
- Evaluate() - Execute JavaScript code and return result as variant
- Global property - Late-binding variant access to the global object (e.g., Engine.Global.myVar := 123)
- RegisterMethod() - Register Delphi methods callable from JavaScript
- TimeoutValue - Script execution timeout control
- GarbageCollect() / MaybeGarbageCollect() - Manual GC control
TQuickJSVariant - Custom variant type for late-binding:
- Allows Pascal code to access JS object properties using variant syntax
- Example: jsObj.propName or jsObj.method(arg1, arg2)
TQuickJSObject - Wrapper for JavaScript object operations:
- HasProperty(), HasOwnProperty()
- GetPropValue(), SetPropValue(), DefineProperty()
- RunMethod() - Call a method on the object
- Root() / UnRoot() - GC protection
This enables straightforward migration of mORMot 1 JavaScript-based examples and provides a convenient API for embedding JavaScript in Delphi/FPC applications.
The implementation leverages the existing mormot.script.core framework and follows mORMot 2 coding conventions.
PR: [https://github.com/synopse/mORMot2/pull/425]
Looking forward to your feedback!
● Hi testgary,
I reviewed your code using Claude and created a feedback issue on your repository:
https://github.com/pit500081/mormot2test/issues/1
Summary of your 4 questions:
Q1 - TUserRec for inter-layer communication:
Good approach. Separating TOrmUser from DTO is correct. However, TUserRec has 23 fields serving too many purposes. Consider splitting into smaller DTOs like TUserProfileRec, TUserLoginResultRec, etc.
Q2 - Captcha in CookieData:
Security concern. Captcha text should NOT be stored client-side (even encrypted). Move to server-side storage using TSynDictionary (Token → CaptchaText mapping). Delete after verification to prevent replay attacks.
Q3 - Separate modules for API and Web:
Architecture is correct - both layers sharing IDomainUserManager is the right design. However:
REST layer (TRestUserManager.Register) is empty - needs implementation
MVC layer is too bloated - Register/Login methods handle captcha, session, validation, response formatting all in one. Extract into separate services (TCaptchaService, etc.) for better testability
Q4 - Overall strategy:
Solid foundation. Well done:
Password hashing with mcfSCrypt
Layered architecture (Repo → Domain → Presentation)
Dependency injection via interfaces
Extends TAuthUser properly
Issues to fix:
GetGroupEnum bug in user.repo.impl.pas:55-56 - SizeOf(TUserGroupTypeEnum) returns 1, not enum count
LoginAt/LoginIp not updated on successful login
No brute-force protection - add login attempt rate limiting
The architecture is sound - these are refinements rather than fundamental changes.
Hi everyone,
I've published a comprehensive guide for RawUtf8-related functions in mORMot 2 on my wiki:
https://github.com/zen010101/Claude_Mor … tion-Guide
This guide covers RawUtf8-related functions organized into categories:
- Type Conversion (To/From RawUtf8)
- UTF-8 Validation and Detection
- String Manipulation (Truncation, Append, Case, Trim)
- Dynamic Array Operations
- Comparison Functions (Standard, ORM, Prefix Matching/Idem)
- Search/Locate Functions
- Formatting, CSV/Array Conversion
- Date/Time ISO8601, JSON, File Operations
- And more...
Each section includes function tables with descriptions and practical code examples.
Hope this helps newcomers and serves as a quick reference for experienced users.
Feedback and corrections are welcome!
Great work!
on the program dir, run "./bin/CsvDynArrayDemo.exe" command in the terminal window. ![]()
Will you commit the working examples to the official repository?
I'm not sure if everyone needs it. I can put it in my repo for a while first.
zen010101 wrote:I haven't built an index for the standard library yet. Could you tell me specifically how you did it? I believe this might greatly prevent AI from using hallucinations to make up non-existent functions.
This is the tool I was referring to; it has the whole source and documentation
https://github.com/JavierusTk/delphi-lookup
Wow, that looks good! I'll have a chance to compare it with Serena. Additionally, I'll look into whether your tool can also support FPC/Lazarus builds.
initialization
// Register RTTI before any CSV parsing
Rtti.RegisterFromText([TypeInfo(TCsvItem), _TCsvItem]);
I would like to add one:
### mormot.app.daemon
Daemon (e.g. Windows Service) Stand-Alone Background Executable Support
- Parent Daemon Settings Class
- Parent Daemon Application Class
### mormot.app.agl
Launch, Watch and Kill Services or Executables from a main Service Instance
- `TSynAngelizeService` Sub-Service Settings and Process
- `TSynAngelize` Main Service Launcher and Watcher
I started using repomix long time ago, but imo that's for chat AI; now with Claude Code I just have mORMot2 documented, and also have a tool I made that had indexed all the libraries I handle, including the Delphi standard lib and of course mORMot2, and the agent can query for an identifier and gets the best result based on FTS and fuzzy search. Tried RAG, but didn't worked (sure it was my fault)
I started using Serena + pasLS (LSP Server for Pascal) for semantic search yesterday. It is included in the Claude Code Plugin store, and I think the effect is quite good. Of course, the official Serena does not support the Pascal/Delphi language. I have made some modifications, and after a test is completed, I will submit a PR to merge into the official master branch. Below is a list of mORMot 2 functions related to RawUtf8: https://gist.github.com/zen010101/3b27c … f89625fe6e
I created a working demo for this: https://github.com/zen010101/Claude_Mor … pping-demo
It loads table/column mappings from mapping_config.json and applies them via OrmMapExternal + MapField at runtime - no recompilation needed.
===========================================
Async Task Demo Client
Demonstrates polling pattern
===========================================
Connected to server
Enter task duration in seconds (1-60): 10
Starting task with 10 second duration...
(Server timeout is 60s, but polling keeps session alive)
Task started with ID: 1
Status: Running... Progress: 4%
Status: Running... Progress: 9%
Status: Running... Progress: 14%
Status: Running... Progress: 20%
Status: Running... Progress: 25%
Status: Running... Progress: 29%
Status: Running... Progress: 35%
Status: Running... Progress: 40%
Status: Running... Progress: 45%
Status: Running... Progress: 50%
Status: Running... Progress: 55%
Status: Running... Progress: 60%
Status: Running... Progress: 65%
Status: Running... Progress: 71%
Status: Running... Progress: 75%
Status: Running... Progress: 80%
Status: Running... Progress: 86%
Status: Running... Progress: 90%
Status: Running... Progress: 95%
Status: COMPLETED!
Result: {"taskId":1,"taskName":"DemoTask","duration":10,"completedAt":"2025-12-17T19:51:42"}
Task finished!
Active tasks on server: 0
Press Enter to exit...
I created a demo demonstrating the polling pattern solution for this issue: https://github.com/zen010101/Claude_Mor … -task-demo
Instead of a single blocking call, the client starts the task and polls GetTaskResult() periodically - each poll updates LastAccessTix10, keeping the session alive without disabling TimeoutSec globally.
I'm really glad you like this article. Actually, I'm thinking about expanding this content into a full wiki in the form of FAQ + technical reviews, but it all depends on my spare time. If I get enough free time, I'll definitely keep enriching the wiki with more detailed and practical content. ![]()
In order to learn this feature, I specifically used Claude to generate a note: https://github.com/zen010101/Claude_Mor … -Explained
Hah, that's only because your AI-friendly comments are so well-written that even the AI can "get" mORMot quickly -- it's like you left a super clear treasure map, and my AI tools just followed the lines faster than I could read them myself. ![]()
Yes, mORMot2 supports reading table/column names from config files using OrmMapExternal + MapField.
Key Mechanism
1. Define ORM class with internal property names (compile-time):
TDbBirthdayRecord = class(TOrmNoCase)
published
property DbCountry: RawUtf8 ...;
property DbBirthdayDate: TDateTime ...;
end;
2. Load mapping from config (runtime):
{
"tableName": "BirthdayCelebration",
"columns": {
"DbCountry": "country_code",
"DbBirthdayDate": "celebration_date"
}
}
3. Apply mapping:
// Map table name
Mapping := OrmMapExternal(Model, TDbBirthdayRecord, ExternalDB, TableName);
// Map column names
Mapping^.MapField('DbCountry', 'country_code');
Mapping^.MapField('DbBirthdayDate', 'celebration_date');
Complete demo: https://gist.github.com/zen010101/713cf … 44f277c1ee
IMO, that kind of simple examples is prefect for anyone approaching mORMot2; what for an expert is "just ..." for a newcomer is "ah, that's what I was looking for"
Maybe we can generate many simpler examples using Claude Code and merge them into the master branch ![]()
Key Differences from OmniThreadLibrary
| Aspect | OmniThreadLibrary | mORMot |
|--------------------|-----------------------|-------------------------------------------|
| Callback signature | procedure(i: integer) | procedure(IndexStart, IndexStop: integer) |
| Granularity | Per-item | Per-range (more efficient) |
| Thread pool | Implicit | Explicit TSynParallelProcess |
| Anonymous methods | Supported | Requires method of object |Full runnable demo is here: https://gist.github.com/zen010101/a69f7 … 41da1424b7
another demo, tested with FPC:
https://gist.github.com/zen010101/4af9a … ebe62f19a9
Usage:
var
Rec: TMyRecord;
begin
// ... populate Rec ...
// Convert from UTC to GMT+8
ConvertRecordDateTimesToTimezone(Rec, TypeInfo(TMyRecord), 8);
// Convert from UTC to EST (GMT-5)
ConvertRecordDateTimesToTimezone(Rec, TypeInfo(TMyRecord), -5);
end;
The parent TSynDaemon.Create signature has no aLog parameter:
constructor TSynDaemon.Create(aSettingsClass: TSynDaemonSettingsClass;
const aWorkFolder, aSettingsFolder, aLogFolder,
aSettingsExt, aSettingsName: TFileName;
aSettingsOptions: TSynJsonFileSettingsOptions;
const aSectionName: RawUtf8);
Logging is actually configured via AfterCreate → fSettings.SetLog(TSynLog).
This appears to be dead code - the parameter exists but does nothing.
The temporal address for it is: http://wp.cyber.es/mORMot2-SAD-Index.html
I really like this version. Even if it doesn't end up being selected as the official version, please keep it.
Thank you again, Javierus, for presenting us with such concise and impactful mORMot 2 materials. I believe this will help many people. ![]()