1

Im currently unable to create a new stream with a prefix, based on the other one.

one example stream can be:

person.admin

Ive been searching around the documentations https://developers.eventstore.com/server/v5/projections.html#user-defined-projections-api

wanted to try fromstreamsmatching, but:

fromStreamsMatching cant be done on a projections that  "one-time".

What do I need to do to allow my Continuous projection to work?

current behaviour:

0@user-

want:

0@user-{USERNAME OR ID}-result

how can I make the projection read from every user ( example user-admin, user-test ), and then return it in user-{username}-result, or user-{username} and create a event with type of result?

Projections:

options({
    resultStreamName: "user-",
    $includeLinks: true,
    reorderEvents: false,
    processingLag: 500
})

fromCategory('user')
    .when({
        $init: function() {
            return {
                logins: 0,
            }
        },
        login: function(state, event) {
            state.logins += 1;
        },

    })
    .outputState()
maria
  • 207
  • 5
  • 22
  • 56

1 Answers1

2

What you are looking for is a stateful partitioned continuous projection.

It needs to do the following:

  • Partition the output state by the user id using the stream name
  • Initialize the partition state by setting the login count to zero
  • Optionally, initialize the shared state with the number of unique users and the total number of logins by setting both to zero
  • Increase the login count by one for each new login event
  • Optionally, increase the total login count in the shared state for each login event
  • When the new partition is created (you got a new user), you can increase the number of users by one
  • Output both states (notice the biState option) to separate streams. The shared state has a static stream name, and the partition state (per user) has a format string where the only argument is the partition id (user id in your case)

Here's the projection code that works:

options({
    $includeLinks: true,
    biState: true
})

fromCategory('user')
    .partitionBy(function (e) { 
        return e.streamId.split("-")[1];
    })
    .when({
        $init: function() {
            return {
                logins: 0,
            }
        },
        $initShared: function() {
            return {
                numberOfUsers: 0,
                totalLogins: 0
            }
        },
        $created: function (s, e) {
            s[1].numberOfUsers++;
        },
        "login": function(s, e) {
            s[0].logins++;
            s[1].totalLogins++;
            return s;
        },

    })
    .outputState()
    .outputTo("totalUsers", "logins-{0}");

When using the biState, you get an array in event handlers and in $created. The first element (index zero) is the partition state, and the second element (index one) is the shared state.

I tested it with two streams: user-123 and user-124, and I emitted a couple of login events to both streams.

When I look at the projection, I see the shared state, and the field to enter the partition id:

Shared state

When I enter the partition id, I can see the partition state:

Partition state

You then can find totalLogins and login-XXX (by user) streams with the latest state. The totalLogins stream would actually contain links to individual login-XXX streams:

enter image description here

Here's how the event in login-XXX looks like:

enter image description here

When the projection emits a new state to the state streams, it also truncates the state stream. So, when you read the stream, I can suggest that you read backwards with the count one. In production, you'd need to scavenge the database regularly, otherwise, those truncated events will occupy a lot of space.

Previous answer

as the question was modified, the answer seems out of context

When I read the docs following the link in your question, I see this:

enter image description here

It says that the argument for the forStreamMatching function is a function that should return true of false given the stream name, like stream => stream.startsWith("person."). I am not sure where the docs say that it would work with a string prefix.

A warning: the ES6 syntax is only supported in ESDB >= 21.10

Alexey Zimarev
  • 17,944
  • 2
  • 55
  • 83