@geoprotocol/geo-sdk 0.19.2 → 0.19.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/examples/ranks/create-ordinal-rank.d.ts +1 -1
- package/dist/examples/ranks/create-ordinal-rank.js +9 -6
- package/dist/examples/ranks/create-ordinal-rank.js.map +1 -1
- package/dist/examples/ranks/create-weighted-rank.d.ts +1 -1
- package/dist/examples/ranks/create-weighted-rank.js +9 -6
- package/dist/examples/ranks/create-weighted-rank.js.map +1 -1
- package/dist/src/abis/dao-space-v2.test.d.ts +2 -0
- package/dist/src/abis/dao-space-v2.test.d.ts.map +1 -0
- package/dist/src/abis/dao-space-v2.test.js +67 -0
- package/dist/src/abis/dao-space-v2.test.js.map +1 -0
- package/dist/src/api-surface.e2e.test.d.ts +2 -0
- package/dist/src/api-surface.e2e.test.d.ts.map +1 -0
- package/dist/src/api-surface.e2e.test.js +1007 -0
- package/dist/src/api-surface.e2e.test.js.map +1 -0
- package/dist/src/client/ranks.d.ts +36 -0
- package/dist/src/client/ranks.d.ts.map +1 -0
- package/dist/src/client/ranks.js +57 -0
- package/dist/src/client/ranks.js.map +1 -0
- package/dist/src/client/ranks.test.d.ts +2 -0
- package/dist/src/client/ranks.test.d.ts.map +1 -0
- package/dist/src/client/ranks.test.js +96 -0
- package/dist/src/client/ranks.test.js.map +1 -0
- package/dist/src/client.d.ts +5 -0
- package/dist/src/client.d.ts.map +1 -1
- package/dist/src/client.js +21 -0
- package/dist/src/client.js.map +1 -1
- package/dist/src/contracts-v2/local-geobrowser.e2e.test.d.ts +2 -0
- package/dist/src/contracts-v2/local-geobrowser.e2e.test.d.ts.map +1 -0
- package/dist/src/contracts-v2/local-geobrowser.e2e.test.js +239 -0
- package/dist/src/contracts-v2/local-geobrowser.e2e.test.js.map +1 -0
- package/dist/src/core/ids/system.d.ts +8 -0
- package/dist/src/core/ids/system.d.ts.map +1 -1
- package/dist/src/core/ids/system.js +8 -0
- package/dist/src/core/ids/system.js.map +1 -1
- package/dist/src/dao-space/propose-update-voting-settings.d.ts +8 -0
- package/dist/src/dao-space/propose-update-voting-settings.d.ts.map +1 -0
- package/dist/src/dao-space/propose-update-voting-settings.js +19 -0
- package/dist/src/dao-space/propose-update-voting-settings.js.map +1 -0
- package/dist/src/dao-space/propose-update-voting-settings.test.d.ts +2 -0
- package/dist/src/dao-space/propose-update-voting-settings.test.d.ts.map +1 -0
- package/dist/src/dao-space/propose-update-voting-settings.test.js +118 -0
- package/dist/src/dao-space/propose-update-voting-settings.test.js.map +1 -0
- package/dist/src/e2e-test-environment.d.ts +26 -0
- package/dist/src/e2e-test-environment.d.ts.map +1 -0
- package/dist/src/e2e-test-environment.js +150 -0
- package/dist/src/e2e-test-environment.js.map +1 -0
- package/dist/src/legacy-api-surface.e2e.test.d.ts +2 -0
- package/dist/src/legacy-api-surface.e2e.test.d.ts.map +1 -0
- package/dist/src/legacy-api-surface.e2e.test.js +860 -0
- package/dist/src/legacy-api-surface.e2e.test.js.map +1 -0
- package/dist/src/ops/index.d.ts +1 -0
- package/dist/src/ops/index.d.ts.map +1 -1
- package/dist/src/ops/index.js +1 -0
- package/dist/src/ops/index.js.map +1 -1
- package/dist/src/ops/ranks.d.ts +33 -0
- package/dist/src/ops/ranks.d.ts.map +1 -0
- package/dist/src/ops/ranks.js +33 -0
- package/dist/src/ops/ranks.js.map +1 -0
- package/dist/src/ops/ranks.test.d.ts +2 -0
- package/dist/src/ops/ranks.test.d.ts.map +1 -0
- package/dist/src/ops/ranks.test.js +36 -0
- package/dist/src/ops/ranks.test.js.map +1 -0
- package/dist/src/ranks/create-rank.d.ts +19 -9
- package/dist/src/ranks/create-rank.d.ts.map +1 -1
- package/dist/src/ranks/create-rank.js +34 -57
- package/dist/src/ranks/create-rank.js.map +1 -1
- package/dist/src/ranks/create-rank.test.js +172 -23
- package/dist/src/ranks/create-rank.test.js.map +1 -1
- package/dist/src/ranks/index.d.ts +0 -1
- package/dist/src/ranks/index.d.ts.map +1 -1
- package/dist/src/ranks/index.js +0 -1
- package/dist/src/ranks/index.js.map +1 -1
- package/dist/src/ranks/types.d.ts +30 -0
- package/dist/src/ranks/types.d.ts.map +1 -1
- package/dist/src/ranks/update-rank.d.ts +40 -0
- package/dist/src/ranks/update-rank.d.ts.map +1 -0
- package/dist/src/ranks/update-rank.js +61 -0
- package/dist/src/ranks/update-rank.js.map +1 -0
- package/dist/src/ranks/update-rank.test.d.ts +2 -0
- package/dist/src/ranks/update-rank.test.d.ts.map +1 -0
- package/dist/src/ranks/update-rank.test.js +131 -0
- package/dist/src/ranks/update-rank.test.js.map +1 -0
- package/dist/src/ranks/vote-ops.d.ts +37 -0
- package/dist/src/ranks/vote-ops.d.ts.map +1 -0
- package/dist/src/ranks/vote-ops.js +99 -0
- package/dist/src/ranks/vote-ops.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { RANK_VOTES_RELATION_TYPE, VOTE_ORDINAL_VALUE_PROPERTY } from '../core/ids/system.js';
|
|
3
|
+
import { Id } from '../id.js';
|
|
4
|
+
import { toGrcId } from '../id-utils.js';
|
|
5
|
+
import { updateRank } from './update-rank.js';
|
|
6
|
+
describe('updateRank', () => {
|
|
7
|
+
const rankId = Id('b1dc6e5c-63e1-43ba-b3d4-755b251a4ea1');
|
|
8
|
+
const movie1Id = Id('f47ac10b-58cc-4372-a567-0e02b2c3d479');
|
|
9
|
+
const movie2Id = Id('550e8400-e29b-41d4-a716-446655440000');
|
|
10
|
+
const spaceId = Id('0e8d692f-1c2d-4f6b-9a0e-7b6d2c1f3a4b');
|
|
11
|
+
const existingRel1 = Id('aaaaaaaa-1111-4111-8111-aaaaaaaaaaaa');
|
|
12
|
+
const existingRel2 = Id('bbbbbbbb-2222-4222-8222-bbbbbbbbbbbb');
|
|
13
|
+
const existingVote1 = Id('cccccccc-3333-4333-8333-cccccccccccc');
|
|
14
|
+
const existingVote2 = Id('dddddddd-4444-4444-8444-dddddddddddd');
|
|
15
|
+
it('deletes the existing vote relations and their entities then re-emits the new votes', () => {
|
|
16
|
+
const result = updateRank({
|
|
17
|
+
rankId,
|
|
18
|
+
rankType: 'ORDINAL',
|
|
19
|
+
votes: [
|
|
20
|
+
{ entityId: movie2Id, spaceId }, // reordered to 1st
|
|
21
|
+
{ entityId: movie1Id, spaceId },
|
|
22
|
+
],
|
|
23
|
+
existingVotes: [
|
|
24
|
+
{ relationId: existingRel1, voteEntityId: existingVote1 },
|
|
25
|
+
{ relationId: existingRel2, voteEntityId: existingVote2 },
|
|
26
|
+
],
|
|
27
|
+
});
|
|
28
|
+
expect(result.id).toBe(rankId);
|
|
29
|
+
expect(result.voteIds).toHaveLength(2);
|
|
30
|
+
// (2 deleteRelation + 2 deleteEntity) + 2 vote relations + 2 vote entities
|
|
31
|
+
expect(result.ops).toHaveLength(8);
|
|
32
|
+
// Deletes come first: each relation is deleted with its reified vote entity
|
|
33
|
+
const del1 = result.ops[0];
|
|
34
|
+
const delEntity1 = result.ops[1];
|
|
35
|
+
const del2 = result.ops[2];
|
|
36
|
+
const delEntity2 = result.ops[3];
|
|
37
|
+
expect(del1.type).toBe('deleteRelation');
|
|
38
|
+
expect(del1.id).toEqual(toGrcId(existingRel1));
|
|
39
|
+
expect(delEntity1.type).toBe('deleteEntity');
|
|
40
|
+
expect(delEntity1.id).toEqual(toGrcId(existingVote1));
|
|
41
|
+
expect(del2.type).toBe('deleteRelation');
|
|
42
|
+
expect(del2.id).toEqual(toGrcId(existingRel2));
|
|
43
|
+
expect(delEntity2.type).toBe('deleteEntity');
|
|
44
|
+
expect(delEntity2.id).toEqual(toGrcId(existingVote2));
|
|
45
|
+
// New vote relations follow
|
|
46
|
+
const voteRel1 = result.ops[4];
|
|
47
|
+
expect(voteRel1.type).toBe('createRelation');
|
|
48
|
+
expect(voteRel1.from).toEqual(toGrcId(rankId));
|
|
49
|
+
expect(voteRel1.to).toEqual(toGrcId(movie2Id));
|
|
50
|
+
expect(voteRel1.relationType).toEqual(toGrcId(RANK_VOTES_RELATION_TYPE));
|
|
51
|
+
expect(voteRel1.toSpace).toEqual(toGrcId(spaceId));
|
|
52
|
+
expect(typeof voteRel1.position).toBe('string');
|
|
53
|
+
// Vote entity carries the ordinal value
|
|
54
|
+
const voteEntity1 = result.ops[5];
|
|
55
|
+
expect(voteEntity1.type).toBe('createEntity');
|
|
56
|
+
const ordinalValue = voteEntity1.values.find(v => v.property.every((b, i) => b === toGrcId(VOTE_ORDINAL_VALUE_PROPERTY)[i]));
|
|
57
|
+
expect(ordinalValue?.value.type).toBe('text');
|
|
58
|
+
});
|
|
59
|
+
it('emits only new votes when there are no existing votes', () => {
|
|
60
|
+
const result = updateRank({
|
|
61
|
+
rankId,
|
|
62
|
+
rankType: 'ORDINAL',
|
|
63
|
+
votes: [{ entityId: movie1Id, spaceId }],
|
|
64
|
+
existingVotes: [],
|
|
65
|
+
});
|
|
66
|
+
// 1 vote relation + 1 vote entity, no deletes
|
|
67
|
+
expect(result.ops).toHaveLength(2);
|
|
68
|
+
expect(result.ops.some(op => op.type === 'deleteRelation')).toBe(false);
|
|
69
|
+
expect(result.ops.some(op => op.type === 'deleteEntity')).toBe(false);
|
|
70
|
+
});
|
|
71
|
+
it('emits only delete ops when the new vote list is empty', () => {
|
|
72
|
+
const result = updateRank({
|
|
73
|
+
rankId,
|
|
74
|
+
rankType: 'ORDINAL',
|
|
75
|
+
votes: [],
|
|
76
|
+
existingVotes: [{ relationId: existingRel1, voteEntityId: existingVote1 }],
|
|
77
|
+
});
|
|
78
|
+
// deleteRelation + deleteEntity for the superseded vote
|
|
79
|
+
expect(result.ops).toHaveLength(2);
|
|
80
|
+
expect(result.ops[0]?.type).toBe('deleteRelation');
|
|
81
|
+
expect(result.ops[1]?.type).toBe('deleteEntity');
|
|
82
|
+
expect(result.voteIds).toHaveLength(0);
|
|
83
|
+
});
|
|
84
|
+
it('throws an error if the rankId is invalid', () => {
|
|
85
|
+
expect(() => updateRank({
|
|
86
|
+
rankId: 'invalid',
|
|
87
|
+
rankType: 'ORDINAL',
|
|
88
|
+
votes: [{ entityId: movie1Id, spaceId }],
|
|
89
|
+
existingVotes: [],
|
|
90
|
+
})).toThrow('Invalid id: "invalid" for `rankId` in `updateRank`');
|
|
91
|
+
});
|
|
92
|
+
it('throws an error if an existing relationId is invalid', () => {
|
|
93
|
+
expect(() => updateRank({
|
|
94
|
+
rankId,
|
|
95
|
+
rankType: 'ORDINAL',
|
|
96
|
+
votes: [{ entityId: movie1Id, spaceId }],
|
|
97
|
+
existingVotes: [{ relationId: 'invalid', voteEntityId: existingVote1 }],
|
|
98
|
+
})).toThrow('Invalid id: "invalid" for `relationId` in `existingVotes` in `updateRank`');
|
|
99
|
+
});
|
|
100
|
+
it('throws an error if an existing voteEntityId is invalid', () => {
|
|
101
|
+
expect(() => updateRank({
|
|
102
|
+
rankId,
|
|
103
|
+
rankType: 'ORDINAL',
|
|
104
|
+
votes: [{ entityId: movie1Id, spaceId }],
|
|
105
|
+
existingVotes: [{ relationId: existingRel1, voteEntityId: 'invalid' }],
|
|
106
|
+
})).toThrow('Invalid id: "invalid" for `voteEntityId` in `existingVotes` in `updateRank`');
|
|
107
|
+
});
|
|
108
|
+
it('throws an error on duplicate (entityId, spaceId) in new votes', () => {
|
|
109
|
+
expect(() => updateRank({
|
|
110
|
+
rankId,
|
|
111
|
+
rankType: 'ORDINAL',
|
|
112
|
+
votes: [
|
|
113
|
+
{ entityId: movie1Id, spaceId },
|
|
114
|
+
{ entityId: movie1Id, spaceId },
|
|
115
|
+
],
|
|
116
|
+
existingVotes: [],
|
|
117
|
+
})).toThrow(`Duplicate (entityId, spaceId) in votes: "${movie1Id}:${spaceId}".`);
|
|
118
|
+
});
|
|
119
|
+
it('accepts ids that are not RFC 4122 UUIDs (arbitrary 16-byte hex)', () => {
|
|
120
|
+
// Geo space IDs are arbitrary 16-byte values; this one has non-spec
|
|
121
|
+
// version ('f') and variant ('f') nibbles.
|
|
122
|
+
const nonRfcSpaceId = Id('73a82967cb12f604f9589ac4bc8024cb');
|
|
123
|
+
expect(() => updateRank({
|
|
124
|
+
rankId,
|
|
125
|
+
rankType: 'WEIGHTED',
|
|
126
|
+
votes: [{ entityId: movie1Id, spaceId: nonRfcSpaceId, value: 1 }],
|
|
127
|
+
existingVotes: [{ relationId: existingRel1, voteEntityId: existingVote1 }],
|
|
128
|
+
})).not.toThrow();
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
//# sourceMappingURL=update-rank.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"update-rank.test.js","sourceRoot":"","sources":["../../../src/ranks/update-rank.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { type Op } from '@geoprotocol/grc-20';
|
|
2
|
+
import type { Id } from '../id.js';
|
|
3
|
+
import type { RankType, Vote } from './types.js';
|
|
4
|
+
/**
|
|
5
|
+
* Validates every vote and enforces `(entityId, spaceId)` uniqueness within a
|
|
6
|
+
* rank. A rank may include the same `entityId` under multiple `spaceId`s (ranking
|
|
7
|
+
* perspectives), so uniqueness is keyed on the pair rather than the entity alone.
|
|
8
|
+
* The pair is compared after normalizing each ID, so a dashed and a dashless form
|
|
9
|
+
* of the same UUID still collide.
|
|
10
|
+
*
|
|
11
|
+
* For `WEIGHTED` ranks every vote must carry a finite numeric `value` — the `Vote`
|
|
12
|
+
* union is not discriminated by `rankType`, so this is enforced at runtime to stop
|
|
13
|
+
* weighted ranks from emitting `undefined` float values.
|
|
14
|
+
*
|
|
15
|
+
* @throws When any `entityId`/`spaceId` is invalid, a weighted vote is missing a
|
|
16
|
+
* finite numeric `value`, or a `(entityId, spaceId)` pair is duplicated.
|
|
17
|
+
*/
|
|
18
|
+
export declare function validateVotes(votes: Vote[], rankType: RankType, context: string): void;
|
|
19
|
+
/**
|
|
20
|
+
* Builds the vote relation + reified vote entity ops for a rank's votes.
|
|
21
|
+
*
|
|
22
|
+
* A fractional index is generated from the array order for every vote and set as
|
|
23
|
+
* the vote relation's `position`, so clients can order votes natively by the
|
|
24
|
+
* relation's `position` field. For ordinal ranks the same fractional index is
|
|
25
|
+
* also stored as the ordinal value on the reified vote entity; weighted ranks
|
|
26
|
+
* store the provided numeric value.
|
|
27
|
+
*
|
|
28
|
+
* Each vote relation also sets `toSpace` to the vote's `spaceId`, scoping the
|
|
29
|
+
* ranked entity to a space perspective.
|
|
30
|
+
*
|
|
31
|
+
* Callers are expected to have validated the votes via {@link validateVotes}.
|
|
32
|
+
*/
|
|
33
|
+
export declare function buildVoteOps(rankId: Id | string, rankType: RankType, votes: Vote[]): {
|
|
34
|
+
ops: Op[];
|
|
35
|
+
voteIds: Id[];
|
|
36
|
+
};
|
|
37
|
+
//# sourceMappingURL=vote-ops.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"vote-ops.d.ts","sourceRoot":"","sources":["../../../src/ranks/vote-ops.ts"],"names":[],"mappings":"AAAA,OAAO,EAKL,KAAK,EAAE,EACR,MAAM,qBAAqB,CAAC;AAO7B,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,UAAU,CAAC;AAEnC,OAAO,KAAK,EAAE,QAAQ,EAAE,IAAI,EAAgB,MAAM,YAAY,CAAC;AAM/D;;;;;;;;;;;;;GAaG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE,QAAQ,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,CAyBtF;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,EAAE,GAAG,MAAM,EAAE,QAAQ,EAAE,QAAQ,EAAE,KAAK,EAAE,IAAI,EAAE,GAAG;IAAE,GAAG,EAAE,EAAE,EAAE,CAAC;IAAC,OAAO,EAAE,EAAE,EAAE,CAAA;CAAE,CAsDjH"}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { createEntity as grcCreateEntity, createRelation as grcCreateRelation, languages, } from '@geoprotocol/grc-20';
|
|
2
|
+
import { generateNJitteredKeysBetween } from 'fractional-indexing-jittered';
|
|
3
|
+
import { RANK_VOTES_RELATION_TYPE, VOTE_ORDINAL_VALUE_PROPERTY, VOTE_WEIGHTED_VALUE_PROPERTY, } from '../core/ids/system.js';
|
|
4
|
+
import { assertValid, generate, toGrcId } from '../id-utils.js';
|
|
5
|
+
function normalizeIdKey(id) {
|
|
6
|
+
return id.replaceAll('-', '').toLowerCase();
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Validates every vote and enforces `(entityId, spaceId)` uniqueness within a
|
|
10
|
+
* rank. A rank may include the same `entityId` under multiple `spaceId`s (ranking
|
|
11
|
+
* perspectives), so uniqueness is keyed on the pair rather than the entity alone.
|
|
12
|
+
* The pair is compared after normalizing each ID, so a dashed and a dashless form
|
|
13
|
+
* of the same UUID still collide.
|
|
14
|
+
*
|
|
15
|
+
* For `WEIGHTED` ranks every vote must carry a finite numeric `value` — the `Vote`
|
|
16
|
+
* union is not discriminated by `rankType`, so this is enforced at runtime to stop
|
|
17
|
+
* weighted ranks from emitting `undefined` float values.
|
|
18
|
+
*
|
|
19
|
+
* @throws When any `entityId`/`spaceId` is invalid, a weighted vote is missing a
|
|
20
|
+
* finite numeric `value`, or a `(entityId, spaceId)` pair is duplicated.
|
|
21
|
+
*/
|
|
22
|
+
export function validateVotes(votes, rankType, context) {
|
|
23
|
+
const seen = new Set();
|
|
24
|
+
for (const vote of votes) {
|
|
25
|
+
assertValid(vote.entityId, `\`entityId\` in \`votes\` in \`${context}\``);
|
|
26
|
+
assertValid(vote.spaceId, `\`spaceId\` in \`votes\` in \`${context}\``);
|
|
27
|
+
if (rankType === 'WEIGHTED') {
|
|
28
|
+
const { value } = vote;
|
|
29
|
+
if (typeof value !== 'number' || !Number.isFinite(value)) {
|
|
30
|
+
throw new Error(`Weighted vote for "${String(vote.entityId)}" must have a finite numeric \`value\` in \`${context}\`.`);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
// Key on the canonical (lowercase, dashless) form so a dashed and a dashless
|
|
34
|
+
// — or differently-cased — spelling of the same UUID still collide.
|
|
35
|
+
const key = `${normalizeIdKey(vote.entityId)}:${normalizeIdKey(vote.spaceId)}`;
|
|
36
|
+
if (seen.has(key)) {
|
|
37
|
+
throw new Error(`Duplicate (entityId, spaceId) in votes: "${String(vote.entityId)}:${String(vote.spaceId)}". Each entity can only be voted once per space perspective in a rank.`);
|
|
38
|
+
}
|
|
39
|
+
seen.add(key);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Builds the vote relation + reified vote entity ops for a rank's votes.
|
|
44
|
+
*
|
|
45
|
+
* A fractional index is generated from the array order for every vote and set as
|
|
46
|
+
* the vote relation's `position`, so clients can order votes natively by the
|
|
47
|
+
* relation's `position` field. For ordinal ranks the same fractional index is
|
|
48
|
+
* also stored as the ordinal value on the reified vote entity; weighted ranks
|
|
49
|
+
* store the provided numeric value.
|
|
50
|
+
*
|
|
51
|
+
* Each vote relation also sets `toSpace` to the vote's `spaceId`, scoping the
|
|
52
|
+
* ranked entity to a space perspective.
|
|
53
|
+
*
|
|
54
|
+
* Callers are expected to have validated the votes via {@link validateVotes}.
|
|
55
|
+
*/
|
|
56
|
+
export function buildVoteOps(rankId, rankType, votes) {
|
|
57
|
+
const ops = [];
|
|
58
|
+
const voteIds = [];
|
|
59
|
+
const fractionalIndices = generateNJitteredKeysBetween(null, null, votes.length);
|
|
60
|
+
votes.forEach((vote, i) => {
|
|
61
|
+
const voteEntityId = generate();
|
|
62
|
+
const relationId = generate();
|
|
63
|
+
const position = fractionalIndices[i];
|
|
64
|
+
voteIds.push(voteEntityId);
|
|
65
|
+
// Relation from rank to voted entity, scoped to the vote's space perspective.
|
|
66
|
+
// `position` carries the order so clients can sort votes by the relation field.
|
|
67
|
+
ops.push(grcCreateRelation({
|
|
68
|
+
id: toGrcId(relationId),
|
|
69
|
+
entity: toGrcId(voteEntityId),
|
|
70
|
+
from: toGrcId(rankId),
|
|
71
|
+
to: toGrcId(vote.entityId),
|
|
72
|
+
relationType: toGrcId(RANK_VOTES_RELATION_TYPE),
|
|
73
|
+
toSpace: toGrcId(vote.spaceId),
|
|
74
|
+
position,
|
|
75
|
+
}));
|
|
76
|
+
const voteValue = rankType === 'ORDINAL'
|
|
77
|
+
? {
|
|
78
|
+
property: toGrcId(VOTE_ORDINAL_VALUE_PROPERTY),
|
|
79
|
+
value: {
|
|
80
|
+
type: 'text',
|
|
81
|
+
value: position,
|
|
82
|
+
language: languages.english(),
|
|
83
|
+
},
|
|
84
|
+
}
|
|
85
|
+
: {
|
|
86
|
+
property: toGrcId(VOTE_WEIGHTED_VALUE_PROPERTY),
|
|
87
|
+
value: {
|
|
88
|
+
type: 'float',
|
|
89
|
+
value: vote.value,
|
|
90
|
+
},
|
|
91
|
+
};
|
|
92
|
+
ops.push(grcCreateEntity({
|
|
93
|
+
id: toGrcId(voteEntityId),
|
|
94
|
+
values: [voteValue],
|
|
95
|
+
}));
|
|
96
|
+
});
|
|
97
|
+
return { ops, voteIds };
|
|
98
|
+
}
|
|
99
|
+
//# sourceMappingURL=vote-ops.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"vote-ops.js","sourceRoot":"","sources":["../../../src/ranks/vote-ops.ts"],"names":[],"mappings":"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"}
|