Do you want to serve arbitrary files?

I think you can use stream server for that:

/// Return physical file contents
/// name - full path to file
ClassMethod serve(name) As %Status
{
    #dim sc As %Status = $$$OK
    #dim %response As %CSP.Response
    //kill %request.Data
    set %request.Data("STREAMOID",1)= ##class(%CSP.StreamServer).Encrypt(##class(%CSP.StreamServer).GetOidForFile(name))
    if ##class(%CSP.StreamServer).OnPreHTTP() {
        set %response.Headers("Content-Disposition")="attachment; filename*=UTF-8''" _ ##class(%CSP.Page).EscapeURL(##class(%File).GetFilename(name), "UTF8")
        set sc = ##class(%CSP.StreamServer).OnPage()
    }

    quit sc
}

Also parameter cannot be assigned.

Files in OS by themselves do not have Content-Type attribute (streams, in web context can have Content-Type attribute). However, knowing file extension Caché has FileClassify utility method that can determine content type. Here's the wrapper I usually use:

/// Determine file mime type
/// name - full path to file
ClassMethod getFileType(name) As %String
{
    set ext = $zcvt($p(name, ".", *), "U")
    do ##class(%CSP.StreamServer).FileClassify(ext, .type , .bin, .charset)
    set ext = "/" _ ext _ "/"
    if ext = "/RTF/" {
        set type = "application/rtf"
    }
    
    return type
}

Or you can additional types into:

 set ^%SYS("CSP","MimeFileClassify", ext) = $lb(type, bin, charset)

That's, I think is an unrelated issue. This SQL:

SELECT JSON_OBJECT('id': '{{}')

Also throws the same error:

[SQLCODE: <-400>:<Fatal error occurred>]
[%msg: <Unexpected error occurred in JSON_OBJECT() function execution of <JSON_OBJECT>.%FromJSON({{}).Parsing error :: Line 1 Offset 2>]

Seems like some data escaping is required.

Very simple escaping (this query executes successfully):

SELECT JSON_OBJECT('id': ' '||'{{}')

Great article, Fabio!

The problem can be that when data changes, then the whole record is copied, i.e. also data which does not change.

That can be solved by checking m%<property> value. It has false positives sometimes (when you change value back, i.e.: a-b-a) and calling log code only if property value has changed.

Forgot that it's only about object access. Still, you have old and new value, so:

Do %code.WriteLine($Char(9,9,9)_"Set tOldValue = {"_tProperty.SqlFieldName_"*O}")
Do %code.WriteLine($Char(9,9,9)_"Set tNewValue = {"_tProperty.SqlFieldName_"*N}")
Do %code.WriteLine($Char(9,9,9)_"if {" _ tProperty.SqlFieldName _ "*C},tOldValue'=tNewValue {")

 

All data changes are logged in a common table.

Why not generate a separate log table for each class, inheriting from logged class, so 1 change == 1 record in log table. It also gives you the possibility of writing "easy" diff tool, so audit results can be displayed conveniently.

If table person has a column "photo" with binary data (stream) containing the photography then each and every time yhe user changes the picture  the role stream is recorded (consuming disk space). 

That problem can be solved by creating immutable stream-containing class.

/// Immutable wrapper for stream
Class User.Document Extends %Persistent
{

/// Global identifier to give to user (prevent id traversal)
Property guid As %String [ InitialExpression = {$system.Util.CreateGUID()}, Required ];

/// Creation date time
Property createDateTime As %TimeStamp [ InitialExpression = {$ZDATETIME($ZTIMESTAMP, 3, 1, 3)};

/// File name as supplied by user (only for display purposes)
Property fileName As %String(MAXLEN =1000) [ Required ];

/// File stream
Property content As %FileBinaryStream;

/// User who uploaded the file
Property user As %String [ InitialExpression = {$username} ];

/// Add new stream
/// realName - real name of a stored file
/// suppliedName - name supplied by user, used only for display purposes
/// stream - stream with data
Method %OnNew(realName As %String = "", suppliedName As %String = "", stream As %Stream.Object = {##class(%FileBinaryStream).%New()}) As %Status [ Private, ServerOnly = 1 ]
{
    #dim sc As %Status = $$$OK
    set ..fileName = ##class(%File).GetFilename(suppliedName)
    set ..content.Filename = realName
    set sc = ..content.CopyFromAndSave(stream)
    quit sc
}

/// Serve file on web
Method serve() As %Status
{
    #dim sc As %Status = $$$OK
    #dim %response As %CSP.Response
    kill %request.Data
    set %request.Data("STREAMOID",1)= ##class(%CSP.StreamServer).Encrypt(..content.%Oid())
    if ##class(%CSP.StreamServer).OnPreHTTP() {
        set %response.Headers("Content-Disposition")="attachment; filename*=UTF-8''"_##class(%CSP.Page).EscapeURL(..fileName,"UTF8")
        set st = ##class(%CSP.StreamServer).OnPage()
    }

    quit sc
}

That way your audit log table would contain only id references.