Rubens Silva · May 12, 2017 go to post

1. Set temporary global:

I though about using that, but I'm targeting the fastest batched feedback as possible.

About $System.Event, I had the code, but switched to IJC, so I'll have to re-implement it. No problem though, I'll redo that asap.

Rubens Silva · May 12, 2017 go to post

Hmm, about I'll have to do something more elaborated then. I had done a quick draft before.

Calling $ZUTIL(132) with no extra arguments makes the current device the principal device.

And how does this affects the parent process? (if it does affect).

Often more useful is the former $ZUTIL(82,12,bool), now ##CLASS(%Device).ReDirectIO(bool) which lets you redirect I/O through routines that can filter, redirect, record, though routines.

I remember reading about that here and here

Rubens Silva · May 17, 2017 go to post

Oh, sorry for my misleading words.
I said "parent process" or "main process" due to the $zparent, $zchild and $job. But I didn't mean something close to process forks, since as you said, they haven't a parent-child relationship, but more liking siblings as they can run independently.
Which situtation would be interesting to change the principal device? This method's description is quite 'dry' to understand what is it's usage.
I noticed that devices are my weak spot, maybe my questions are looking dumb for some people here. :P
Thanks for your help so far.

Rubens Silva · May 19, 2017 go to post

Wow, it worked! But I don't want it to send the data back to the other process for each iteration, so I tried buffering it using a variable. Now, If you run this code you will notice the following error:
 [CRLF] class '%Studio.General', method 'Execute': <WRITE> 41 zExecute+26^%Studio.General.1
The routine might according to the principal device, of course.
There're two points I noticed here:
1 - The caller process must wait for the job for it's first write. Or a <READ> error will happen.
2 - The caller process must know when stop iterating if the job is finished.
3 - Sometime the line break works, sometimes it doesn't.
That's why I tried introducing the Event API, however it doesn't seems to be working, since the Signal is being ignored by that WaitMsg. Is there anything I'm missing here?
Class Log.Test [ Abstract ]
{
ClassMethod Write(
device As %String,
start As %Integer = 1) As %Boolean
{
  set startEventTriggered = 0
  open device:("127.0.0.1":33568:"M"):3
  quit:'$test
  
  use device
  
  set buf = ""
  set msg = "This is a test #"
  
  for i=start:1:10460 {
  set composedMessage = msg_i_$c(13, 10)_" [CRLF] "
  set expectedSize = $length(buf) + $length(composedMessage)
  if expectedSize > $$$MaxStringLength {
  //set offset = expectedSize - $$$MaxStringLength
  //set subbuff = $e
  if 'startEventTriggered {
  set startEventTriggered = 1
  use 0
  do $System.Event.Signal($zparent, "1")
  use device
  }
   write buf
   set buf = ""
  else {
   set buf = buf_composedMessage
  
  }
  
  if buf '= "" write buf
  write $c(0)
  
  close device
}
ClassMethod Start()
{
   set server = "|TCP|1"
   set client = "|TCP|2"    
   
   job ..Write(server)::3
   
   do $System.Event.WaitMsg("", 3)
   
   open client:(:33568:"M"):3 // Moved open outside the loop
   quit:'$test 
   
   while 1 {
     use "|TCP|2"
     read x:3
     
     if '= "" {
       use 0
       write x
     }     
     quit:x=$c(0)
   }   
   close client
}
}
 

Rubens Silva · May 22, 2017 go to post

If you want a proof-of-concept or a showcase, then this link shows how the class package hierarchy is followed.

You can disable the use of .txt using two ways: 
1 - By running ##class(Port.SourceControl.Config).SetSourceExtension("")

2 - By running the wizard with ##class(Port.SourceControl.Wizard).Start() and navigating to the relevant option.

Note that this will ONLY AFFECT the appended extension, not the type itself. Example:
##class(Port.SourceControl.Config).SetSourceExtension("")  = cls/Port/SourceControl/Hooks.cls
##class(port.SourceControl.Config).SetSourceExtension("txt")  = cls/Port/SourceControl/Hooks.cls.txt
All the same for following formats: INC, INT, MAC, DFI, MVB, MVI, BAS. Except if the file is inside the web (CSP) path.
I could do a small screencast, but the usage is pretty straightforward. Anything more advanced than that is covered by the Wizard.
How to use:
1 - First time only:  import the port.xml.

2 - Run ##class(Port.SourceControl.Installer).Install().

3 - Restart the Studio.

4 - Done! Now whenever you save a file related to the project, it's structure is generated and the file is exported everytime you save it.

5 - If you want to export all items regarding the current project you can use Source Control->Export Current Project.
EDIT: Oh I forgot mentioning about tests.
Unit testing with Port:
As long as you have classes prefixed with the package "UnitTest" (which is also configurable). You can export these classes to XML and run their tests atomically.
1 - SourceControl->Export Test Suites to XML.

2 - Context SourceControl menu->Run Tests Associated with this item.

By default when you install Port using the installer, it sets a parameter to run unit tests as you keep compiling the classes.
You can disable that as well.
If you notice any bugs, please fill a issue.
If you still need a demonstration, please ask again.

Rubens Silva · May 22, 2017 go to post

Just like a CSP file.
You have two ways of working with that:

1 - If you modify the file using the Studio, that file is exported automatically when you save it.

2 - If you modify the file outside the Studio you can import the file again to the project and it'll be overwritten with the new version (as long as it's newer).
Anything inside the web folder is mirrored inside the csp application. Which means:
If you have a file with the path 
C:\CacheProjects\yourproject\web\css\index.css 
It's name will be resolved to:
csp/namespace/index.css
And will be mirrored like this:
C:\InterSystems\Cache\CSP\namespace\css\index.css
If you remove that file from the project, save it and export, then this file will be eliminated from the repository since it doesn't belong to the project anymore.
The same rule is applied for each type. Not only the folder "web".

Rubens Silva · May 23, 2017 go to post

This indeed made the communication much easier!
Now I only have to create a strategy for message batching.
It seems that I can't simply use WaitForComplete since it puts the caller process into sleep.
My next step is do an experiment with the IPQ variation.
Anyway, here's my experiment using the WorkMgr.

Class Log.Test2 [ Abstract ]
{
ClassMethod Log()
{
  set buf = ""
  set msg = "This is a test #"
  
  for i=1:1:1046000 {
    set composedMessage = msg_i_$c(13, 10)
    set expectedSize = $length(buf) + $length(composedMessage)
    if expectedSize > $$$MaxStringLength {
      write buf
      set buf = ""
    else {
      set buf = buf_composedMessage
    
  }
  
  if buf '= "" write buf
  quit $$$OK
}
ClassMethod Start()
{
  write "This is from "_$job, !!
  set queue = $System.WorkMgr.Initialize("d", .sc, .sc)
  set sc = queue.Queue("##class(Log.Test2).Log")
  if $$$ISERR(sc) do $System.OBJ.DisplayError(sc) quit sc
  set sc = queue.WaitForComplete()
  if $$$ISERR(sc) do $System.OBJ.DisplayError(scquit sc
  quit $$$OK
}
}

Rubens Silva · May 25, 2017 go to post

If you want something more regular you could pipe the output via OS:
This also outputs more than 32000 kb.

 set errorLogDir = ##class(%File).TempFilename()
 set outputLogDir = ##class(%File).TempFilename()

 set command = "dir /A-D /B /S ""%1"" 2> ""%2"" > ""%3"""
 quit $zf(-1, $$$FormatText(command, "C:\InterSystems\Cache", errorLogDir, outputLogDir))
Now simply open and read the logs.

Rubens Silva · May 26, 2017 go to post

I don't recommend opening %ResultSet instances recursively.
It's more performatic if you open a single %SQL.Statement  and reuse that.
This will also solve you quite some bytes being allocated for the process as the depth keeps growing.

Rubens Silva · May 26, 2017 go to post

What the ... ? wow.
I'll have to update my code if that's true for every case. Could you measure the execution time for both approaches?

Rubens Silva · May 26, 2017 go to post

Vitaliy is faster that's probably because Caché is delegating the control to the OS's native API.

That is indeed the best approach, and could be made cross-platform by using $$$isUNIX, $$is$WINDOWS and $$$isVMS.
Now I gotta say, I'm impressed by these results. 


Ex: Using find -iname %1 instead of dir.
Then, what's the advantage of using %SQL.Statement over %ResultSet? The only reason I can think about is for using it's metadata now.
EDIT: Ahh, there's a detail. %FileSet is not persisted, neither is using SQL. It's a custom query being populated by $zsearch internally.
Maybe for SQL based queries %SQL.Statement would be better.

Rubens Silva · May 26, 2017 go to post

Here.
If you need an explanation, the method I used for example is part of an algorithm that keeps the repository in-sync with the 

the project.
You're right, your approach is the best, even though it's not cross-platform. Such issue could be solved by using $$$isUNIX

and $$$isVMS though.
Something like:
if $$$isUNIX set command = "find %1"

Rubens Silva · May 31, 2017 go to post

Outstanding!
I'll keep a close eye on that thread.
Since you're using something close to decorators/annotations, does that means you made a COS syntax parser, lexer and AST?
I'm also very interested about how fast and what strategies you used for parsing JSONs.
Here is a techical background:
So far, when realising performance tests I had to opt-out from using the default %Stream classes's MoveTo method, since it always rewinds the position and moves the pointer , this caused me great performance headaches. So I had to implement some sort of algorithm that lazy loads and calculates the position.
I'm wondering if you had to implement something focused on older Caché versions and faced such issues, because that was my case. I had to implement a JSON parser for the 2010 version, due to our client and partner restrictions.

Rubens Silva · May 31, 2017 go to post

It does (EDIT: nope, it doesn't, because it's easier). I use it for publishing (or making RPC-aware methods).
And it doesn't even requires PropertyClass, instead of properties.
Yeah, I also had to do something along these lines.
Though I can't publish the source code for now, since it does contains business related authentication code.

Rubens Silva · May 31, 2017 go to post

90 and 100 lines for serializers is quite the achievement.
Oh, I see, so that's how you introduced decorators.
Now about JSON parsing...
In my case I had to provide out-of-the-box support for streams and keep the parsing process the faster as I could, since we intended to use it for parsing files that weighted around 5~8 MBs to populate the database. CSV or XML wasn't an option at that time.
I can provide the code for that.
About NodeJS ORM:
Also, how are you connecting NodeJS to Caché? Is it using the native driver provided by InterSystems? If so, are you dealing with globals directly or did you implemented an adapter for NoSQL?

Rubens Silva · May 31, 2017 go to post

Here's how we did.
It works for 2010, in that case I expose the controller methods and using &p1=id ... &pN=idN to retrieve a JSON collection of 

SamplePerson serialized instances.
Notice PUBLIC, methods that have this parameter are available to be called over HTTP.
Method getUsers(args... As Sample.Person) As %ListOfObjects(PUBLIC=1)
{
  set persons = ##class(%ListOfObjects).%New()
  set sumEq = ""
  set sum = 0
  for i=1:1:args {
    do persons.Insert(args(i))
  }
  quit persons
}
I'll check if I can remove the proprietary code and make it available as well.

Rubens Silva · May 31, 2017 go to post

I do suppose it's easier to extract parameters from ReturnTypeParams than parsing then through property documentations. Especially if there's a case with multiple parameters. So I think that's more a technical debt situation than an end-user preference.

Rubens Silva · May 31, 2017 go to post

Interesting...
Can you provide some example of usage for that node package?

Rubens Silva · May 31, 2017 go to post

Here's a snapshot of my last test run.
It's portuguese though. But I think you can figure the essentials.


There's a few things to notice (and some to fix).
1 - The biggest file is MG.json, it's a JSON containing all coordinates to render the biggest state from here. You can see it takes about 6.5 minutes to parse all data. But it's a special case, not something common to be parsed. Btw, I said 5~8 MB, but eh... it's about 3 MB (3.810.312 bytes), my mistake.

2 - blns.json.txt is a nasty sure-fire JSON file. I took it from this repo and trying to figure how to solve a parsing issue. If you want something to risk breaking your parser, really, try this file.
3 -Some chars below might not be shown correctly, that is due to unicode support. That's  because of assertions related to unescaping paired unicode sequences like:  \uD83D\uDE02 that generates a smile.

tests begins ...UnitTest.JQX.JSON.Parser begins ...TestArrayMiscParsing() begins ...AssertEquals:Array precisa conter 6 valores (passed)AssertEquals:Posição 1 do array deve ser um objeto (passed)AssertEquals:Posição 1 do array deve ser um proxy (passed)AssertEquals:Proxy deve conter propriedade 'foo' definido com valor 'bar' (passed)AssertEquals:Proxy deve conter propriedade 'foo' definida como outro proxy (passed)AssertEquals:Posição 2 do array deve um %ListOfDataTypes (passed)AssertEquals:Deve conter 5 valores (passed)AssertEquals:Posição 1 deve ser ter valor 1 (passed)AssertEquals:Posição 2 deve ser ter valor 2 (passed)AssertEquals:Posição 3 deve ser ter valor 3 (passed)AssertEquals:Posição 4 deve ser um %ListOfDataTypes (passed)AssertEquals:O array dessa posição deve ter um item (passed)AssertEquals:Com o valor 15 (passed)AssertEquals:Posição 5 deve ser um proxy (passed)AssertEquals:O proxy deve ter uma propriedade 'well' com valor 'done' (passed)AssertEquals:De volta ao array principal, a posição 3 deve ser null (passed)AssertEquals:De volta ao array principal, a posição 4 deve ser 'null' equivalendo null (passed)AssertEquals:De volta ao array principal, a posição 5 deve ser true (passed)AssertEquals:De volta ao array principal, a posição 6 deve ser 'true' equivalendo true (passed)LogMessage:Duration of execution: .269543 sec.TestArrayMiscParsing passedTestArrayNumberParsing() begins ...AssertEquals:Array precisa conter 3 valores (passed)AssertEquals:Posição 1 do array deve ser 1 (passed)AssertEquals:Posição 2 do array deve ser 2 (passed)AssertEquals:Posição 3 do array deve ser 2.50 (passed)LogMessage:Duration of execution: .045573 sec.TestArrayNumberParsing passedTestArrayReservedParsing() begins ...AssertEquals:Array precisa conter 3 valores (passed)AssertEquals:Posição 1 do array deve ser true (passed)AssertEquals:Posição 2 do array deve ser false (passed)AssertEquals:Posição 3 do array deve ter null (passed)LogMessage:Duration of execution: .047348 sec.TestArrayReservedParsing passedTestArrayStringParsing() begins ...AssertEquals:Array precisa conter 3 valores (passed)AssertEquals:Posição 1 do array deve ter 'quite' (passed)AssertEquals:Posição 2 do array deve ter 'some' (passed)AssertEquals:Posição 3 do array deve ter 'string' (passed)LogMessage:Duration of execution: .052526 sec.TestArrayStringParsing passedTestEscapedStringParsing() begins ...AssertEquals:Deve retornar um sorriso �� (passed)AssertEquals:'a' deve ter valor 1 (passed)AssertEquals:'c' deve ter conter 3 itens (passed)AssertEquals:A primeira posição deve conter o sorriso de novo �� (passed)AssertEquals:Deve ter a seguinte mensagem: Isso é um "teste" �� (passed)AssertEquals:Deve conter um objeto vazio (passed)LogMessage:Duration of execution: .104971 sec.TestEscapedStringParsing passedTestMultilineParsing() begins ...LogMessage:LogMessage:*******************************************************LogMessage:*** <!> Testando arquivo blns.json.txt <!> ***LogMessage:*******************************************************LogMessage:ERRO #5001: Syntax Error: Expected value separator or array ending, but found ASCII code 92 at offset 207LogMessage:LogMessage:*******************************************************LogMessage:*** <!> Testando arquivo example.json <!> ***LogMessage:*******************************************************LogMessage:LogMessage:Duração em segundos: .07764LogMessage:Início do parsing: 05/31/2017 13:43:38LogMessage:Fim do parsing: 05/31/2017 13:43:38AssertEquals:Deve retornar um objeto a partir do arquivo example.json (passed)AssertEquals:id deve ter valor igual a string 0001 (passed)AssertEquals:type deve ter valor igual a string donut (passed)AssertEquals:name deve ter valor igual a string Cake (passed)AssertEquals:ppu deve ter valor numerico igual .55 (passed)AssertEquals:A propriedade batter deve ter 4 itens (passed)AssertEquals:id de batter deve ter valor igual 1001 (passed)AssertEquals:type de batter deve ter valor igual Regular (passed)AssertEquals:id de batter deve ter valor igual 1002 (passed)AssertEquals:type de batter deve ter valor igual Chocolate (passed)AssertEquals:id de batter deve ter valor igual 1003 (passed)AssertEquals:type de batter deve ter valor igual Blueberry (passed)AssertEquals:id de batter deve ter valor igual 1004 (passed)AssertEquals:type de batter deve ter valor igual Devil's Food (passed)AssertEquals:id de topping deve ter valor igual 5001 (passed)AssertEquals:type de topping deve ter valor igual None (passed)AssertEquals:id de topping deve ter valor igual 5002 (passed)AssertEquals:type de topping deve ter valor igual Glazed (passed)AssertEquals:id de topping deve ter valor igual 5005 (passed)AssertEquals:type de topping deve ter valor igual Sugar (passed)AssertEquals:id de topping deve ter valor igual 5007 (passed)AssertEquals:type de topping deve ter valor igual Powdered Sugar (passed)AssertEquals:id de topping deve ter valor igual 5006 (passed)AssertEquals:type de topping deve ter valor igual Chocolate with Sprinkles (passed)AssertEquals:id de topping deve ter valor igual 5003 (passed)AssertEquals:type de topping deve ter valor igual Chocolate (passed)AssertEquals:id de topping deve ter valor igual 5004 (passed)AssertEquals:type de topping deve ter valor igual Maple (passed)AssertEquals:id deve ter valor igual a string 0002 (passed)AssertEquals:type deve ter valor igual a string donut (passed)AssertEquals:name deve ter valor igual a string Raised (passed)AssertEquals:ppu deve ter valor numerico igual .55 (passed)AssertEquals:A propriedade batter deve ter 1 itens (passed)AssertEquals:id de batter deve ter valor igual 1001 (passed)AssertEquals:type de batter deve ter valor igual Regular (passed)AssertEquals:id de topping deve ter valor igual 5001 (passed)AssertEquals:type de topping deve ter valor igual None (passed)AssertEquals:id de topping deve ter valor igual 5002 (passed)AssertEquals:type de topping deve ter valor igual Glazed (passed)AssertEquals:id de topping deve ter valor igual 5005 (passed)AssertEquals:type de topping deve ter valor igual Sugar (passed)AssertEquals:id de topping deve ter valor igual 5003 (passed)AssertEquals:type de topping deve ter valor igual Chocolate (passed)AssertEquals:id de topping deve ter valor igual 5004 (passed)AssertEquals:type de topping deve ter valor igual Maple (passed)AssertEquals:id deve ter valor igual a string 0003 (passed)AssertEquals:type deve ter valor igual a string donut (passed)AssertEquals:name deve ter valor igual a string Old Fashioned (passed)AssertEquals:ppu deve ter valor numerico igual .55 (passed)AssertEquals:A propriedade batter deve ter 2 itens (passed)AssertEquals:id de batter deve ter valor igual 1001 (passed)AssertEquals:type de batter deve ter valor igual Regular (passed)AssertEquals:id de batter deve ter valor igual 1002 (passed)AssertEquals:type de batter deve ter valor igual Chocolate (passed)AssertEquals:id de topping deve ter valor igual 5001 (passed)AssertEquals:type de topping deve ter valor igual None (passed)AssertEquals:id de topping deve ter valor igual 5002 (passed)AssertEquals:type de topping deve ter valor igual Glazed (passed)AssertEquals:id de topping deve ter valor igual 5003 (passed)AssertEquals:type de topping deve ter valor igual Chocolate (passed)AssertEquals:id de topping deve ter valor igual 5004 (passed)AssertEquals:type de topping deve ter valor igual Maple (passed)LogMessage:LogMessage:*******************************************************LogMessage:*** <!> Testando arquivo MG.json <!> ***LogMessage:*******************************************************LogMessage:LogMessage:Duração em segundos: 395.318218LogMessage:Início do parsing: 05/31/2017 13:43:39LogMessage:Fim do parsing: 05/31/2017 13:50:14AssertEquals:Deve retornar um objeto a partir do arquivo MG.json (passed)LogMessage:LogMessage:*******************************************************LogMessage:*** <!> Testando arquivo municipios.json <!> ***LogMessage:*******************************************************LogMessage:LogMessage:Duração em segundos: 10.035081LogMessage:Início do parsing: 05/31/2017 13:50:15LogMessage:Fim do parsing: 05/31/2017 13:50:25AssertEquals:Deve retornar um objeto a partir do arquivo municipios.json (passed)LogMessage:LogMessage:*******************************************************LogMessage:*** <!> Testando arquivo package.json <!> ***LogMessage:*******************************************************LogMessage:LogMessage:Duração em segundos: .057244LogMessage:Início do parsing: 05/31/2017 13:50:25LogMessage:Fim do parsing: 05/31/2017 13:50:25AssertEquals:Deve retornar um objeto a partir do arquivo package.json (passed)LogMessage:LogMessage:*******************************************************LogMessage:*** <!> Testando arquivo random.json <!> ***LogMessage:*******************************************************LogMessage:LogMessage:Duração em segundos: .518465LogMessage:Início do parsing: 05/31/2017 13:50:25LogMessage:Fim do parsing: 05/31/2017 13:50:25AssertEquals:Deve retornar um objeto a partir do arquivo random.json (passed)LogMessage:LogMessage:*******************************************************LogMessage:*** <!> Testando arquivo random_bigger.json <!> ***LogMessage:*******************************************************LogMessage:LogMessage:Duração em segundos: 1.920557LogMessage:Início do parsing: 05/31/2017 13:50:25LogMessage:Fim do parsing: 05/31/2017 13:50:27AssertEquals:Deve retornar um objeto a partir do arquivo random_bigger.json (passed)LogMessage:LogMessage:*******************************************************LogMessage:*** <!> Testando arquivo schema.json <!> ***LogMessage:*******************************************************LogMessage:LogMessage:Duração em segundos: .03808LogMessage:Início do parsing: 05/31/2017 13:50:27LogMessage:Fim do parsing: 05/31/2017 13:50:27AssertEquals:Deve retornar um objeto a partir do arquivo schema.json (passed)AssertEquals:Deve conter uma propriedade $schema com o valor http://json-schema.org/draft-04/schema# (passed)AssertEquals:Deve conter um objeto chamado 'definitions' (passed)AssertEquals:Deve conter um objeto chamado 'address' (passed)AssertEquals:Deve conter uma propriedade 'type' em 'address' com o valor object (passed)AssertEquals:Deve conter um objeto chamado 'properties' em 'address' (passed)AssertEquals:Deve conter um objeto chamado 'street_address' em 'properties' (passed)AssertEquals:Deve conter uma propriedade 'type' em 'street_address' com o valor string (passed)AssertEquals:Deve conter um objeto chamado 'city' em 'properties' (passed)AssertEquals:Deve conter uma propriedade 'type' em 'city' com o valor string (passed)AssertEquals:Deve conter um objeto chamado 'state' em 'properties' (passed)AssertEquals:Deve conter uma propriedade 'type' em 'state' com o valor string (passed)AssertEquals:Deve conter uma coleção em 'address' chamada 'required' (passed)AssertEquals:Deve conter 3 valores na coleção mencionada (passed)AssertEquals:Posicao 1 deve ter 'street_address' (passed)AssertEquals:Posicao 2 deve ter 'city' (passed)AssertEquals:Posicao 3 deve ter 'state' (passed)LogMessage:LogMessage:*******************************************************LogMessage:*** <!> Testando arquivo youtube.json <!> ***LogMessage:*******************************************************LogMessage:LogMessage:Duração em segundos: .092909LogMessage:Início do parsing: 05/31/2017 13:50:27LogMessage:Fim do parsing: 05/31/2017 13:50:28AssertEquals:Deve retornar um objeto a partir do arquivo youtube.json (passed)LogMessage:Duration of execution: 409.506008 sec.TestMultilineParsing passedTestObjectKeyArrayValueParsing() begins ...AssertEquals:Objeto deve ser um proxy (passed)AssertEquals:Objeto precisa conter propriedade 'a' com valor de um array (passed)LogMessage:Duration of execution: .012107 sec.TestObjectKeyArrayValueParsing passedTestObjectKeyFalseValueParsing() begins ...AssertEquals:Objeto deve ser um proxy (passed)AssertEquals:Objeto precisa conter propriedade 'a' com valor true (passed)LogMessage:Duration of execution: .012377 sec.TestObjectKeyFalseValueParsing passedTestObjectKeyNullValueParsing() begins ...AssertEquals:Objeto deve ser um proxy (passed)AssertEquals:Objeto precisa conter propriedade 'a' com valor null (passed)LogMessage:Duration of execution: .015294 sec.TestObjectKeyNullValueParsing passedTestObjectKeyObjectValueParsing() begins ...AssertEquals:Objeto deve ser um proxy (passed)AssertEquals:Objeto precisa conter propriedade 'a' com valor de um proxy (passed)LogMessage:Duration of execution: .01224 sec.TestObjectKeyObjectValueParsing passedTestObjectKeyStringValueParsing() begins ...AssertEquals:Objeto deve ser um proxy (passed)AssertEquals:Objeto precisa conter propriedade 'a' com valor 'string' (passed)LogMessage:Duration of execution: .012922 sec.TestObjectKeyStringValueParsing passedTestObjectKeyTrueValueParsing() begins ...AssertEquals:Objeto deve ser um proxy (passed)AssertEquals:Objeto precisa conter propriedade 'a' com valor true (passed)LogMessage:Duration of execution: .012159 sec.TestObjectKeyTrueValueParsing passedTestObjectMultiKeyObjectValueParsing() begins ...AssertEquals:Objeto deve ser um proxy (passed)AssertEquals:Objeto precisa conter propriedade 'a' como sendo um proxy (passed)AssertEquals:Objeto precisa conter propriedade 'b' como sendo um proxy (passed)AssertEquals:'b' deve ter foo definido com valor 'bar (passed)LogMessage:Duration of execution: .024768 sec.TestObjectMultiKeyObjectValueParsing passedTestObjectMultiKeyStringValueParsing() begins ...AssertEquals:Objeto deve ser um proxy (passed)AssertEquals:Objeto precisa conter propriedade 'a' com valor 'string' (passed)AssertEquals:Objeto precisa conter propriedade 'b' com valor 'foo' (passed)LogMessage:Duration of execution: .019991 sec.TestObjectMultiKeyStringValueParsing passedUnitTest.JQX.JSON.Parser passedSkipping deleting classes tests passed
Rubens Silva · Jun 1, 2017 go to post
for i=1:1:collection.Count() {set item = collection.GetAt(i)// From this point it depends of the type of item you're storing.// You must know to fetch each property values.// Ex: set value = item.property (%ZEN.proxyObject)// set value = item.GetAt("property") (%ArrayOfDataTypes)}

Change the values and reassign it to each related property.

do item.SetAt("property", newValue)
set item.property = newValue
Rubens Silva · Jun 1, 2017 go to post

Thanks for posting about it's usage.
It's looks pretty straightforward and with a low learning curve.
You mentioned earlier about using TCP over CSP, does that means you implemented a messaging protocol over TCP layer instead of using HTTP? Is that lib restricted for NodeJS usage or it could be webpack'ed for example? Nevermind, I just re-read the part where you mentioned about client API.
How do you deal with license usage? How much does it escalates with a fair amount of users and how do you manage all of that?

Rubens Silva · Jun 1, 2017 go to post

I see, is there some kind of security implemented or planned? Like an authentication mechanism.
Also, one single license. I'm starting to see how things work now, if I understood correctly most of the heavy-lifting is done using NodeJS.
Similar to sending a payload with multiple requests from multiple client connections, limited by a threshould, let's say... about 1000 requests. So, Caché simply resolves all of them and return them at once. NodeJS looks what's relevant for each client and forwards the filtered response.
But that is only possible if you host  an intermediate server, do you use something like Express?

Rubens Silva · Jun 2, 2017 go to post

%DynamicAbstractObject instances have a method %Set(key, value) that you can use to insert new or update existing values.
set o = { "foo": "bar" }
write o.%ToJSON() // {"foo": "bar"}
do o.%Set("foo", "bars")
write o.%ToJSON() // {"foo": "bars"}

Rubens Silva · Jun 2, 2017 go to post

Older Caché versions don't have that second parameter.
I also discovered about this on the hardest way: breaking the build.

Rubens Silva · Jun 2, 2017 go to post

Oh, cool.
Too bad that %ResultSet for Caché 2010 doesn't have this property metadata though.
I guess I'll need to workaround it and disable the feature I'm trying to implement.