@walkeros/server-transformer-fingerprint 4.0.0 → 4.0.1-next-1778183328892

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/dev.d.mts ADDED
@@ -0,0 +1,48 @@
1
+ import * as _walkeros_core_dev from '@walkeros/core/dev';
2
+ import { z } from '@walkeros/core/dev';
3
+ import { Flow, Hint } from '@walkeros/core';
4
+
5
+ /**
6
+ * Fingerprint transformer settings schema.
7
+ *
8
+ * Mirrors: types.ts FingerprintSettings
9
+ */
10
+ declare const SettingsSchema: z.ZodObject<{
11
+ fields: z.ZodArray<z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
12
+ key: z.ZodOptional<z.ZodString>;
13
+ value: z.ZodOptional<z.ZodUnknown>;
14
+ fn: z.ZodOptional<z.ZodString>;
15
+ }, z.core.$strip>]>>;
16
+ output: z.ZodOptional<z.ZodString>;
17
+ length: z.ZodOptional<z.ZodNumber>;
18
+ }, z.core.$strip>;
19
+ type Settings = z.infer<typeof SettingsSchema>;
20
+
21
+ declare const settings: _walkeros_core_dev.JSONSchema;
22
+
23
+ type index$1_Settings = Settings;
24
+ declare const index$1_SettingsSchema: typeof SettingsSchema;
25
+ declare const index$1_settings: typeof settings;
26
+ declare namespace index$1 {
27
+ export { type index$1_Settings as Settings, index$1_SettingsSchema as SettingsSchema, index$1_settings as settings };
28
+ }
29
+
30
+ declare const serverFingerprint: Flow.StepExample;
31
+ declare const missingFields: Flow.StepExample;
32
+ declare const ipAnonymization: Flow.StepExample;
33
+
34
+ declare const step_ipAnonymization: typeof ipAnonymization;
35
+ declare const step_missingFields: typeof missingFields;
36
+ declare const step_serverFingerprint: typeof serverFingerprint;
37
+ declare namespace step {
38
+ export { step_ipAnonymization as ipAnonymization, step_missingFields as missingFields, step_serverFingerprint as serverFingerprint };
39
+ }
40
+
41
+ declare const index_step: typeof step;
42
+ declare namespace index {
43
+ export { index_step as step };
44
+ }
45
+
46
+ declare const hints: Hint.Hints;
47
+
48
+ export { index as examples, hints, index$1 as schemas };
package/dist/dev.d.ts ADDED
@@ -0,0 +1,48 @@
1
+ import * as _walkeros_core_dev from '@walkeros/core/dev';
2
+ import { z } from '@walkeros/core/dev';
3
+ import { Flow, Hint } from '@walkeros/core';
4
+
5
+ /**
6
+ * Fingerprint transformer settings schema.
7
+ *
8
+ * Mirrors: types.ts FingerprintSettings
9
+ */
10
+ declare const SettingsSchema: z.ZodObject<{
11
+ fields: z.ZodArray<z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
12
+ key: z.ZodOptional<z.ZodString>;
13
+ value: z.ZodOptional<z.ZodUnknown>;
14
+ fn: z.ZodOptional<z.ZodString>;
15
+ }, z.core.$strip>]>>;
16
+ output: z.ZodOptional<z.ZodString>;
17
+ length: z.ZodOptional<z.ZodNumber>;
18
+ }, z.core.$strip>;
19
+ type Settings = z.infer<typeof SettingsSchema>;
20
+
21
+ declare const settings: _walkeros_core_dev.JSONSchema;
22
+
23
+ type index$1_Settings = Settings;
24
+ declare const index$1_SettingsSchema: typeof SettingsSchema;
25
+ declare const index$1_settings: typeof settings;
26
+ declare namespace index$1 {
27
+ export { type index$1_Settings as Settings, index$1_SettingsSchema as SettingsSchema, index$1_settings as settings };
28
+ }
29
+
30
+ declare const serverFingerprint: Flow.StepExample;
31
+ declare const missingFields: Flow.StepExample;
32
+ declare const ipAnonymization: Flow.StepExample;
33
+
34
+ declare const step_ipAnonymization: typeof ipAnonymization;
35
+ declare const step_missingFields: typeof missingFields;
36
+ declare const step_serverFingerprint: typeof serverFingerprint;
37
+ declare namespace step {
38
+ export { step_ipAnonymization as ipAnonymization, step_missingFields as missingFields, step_serverFingerprint as serverFingerprint };
39
+ }
40
+
41
+ declare const index_step: typeof step;
42
+ declare namespace index {
43
+ export { index_step as step };
44
+ }
45
+
46
+ declare const hints: Hint.Hints;
47
+
48
+ export { index as examples, hints, index$1 as schemas };
package/dist/dev.js ADDED
@@ -0,0 +1 @@
1
+ "use strict";var e,t=Object.defineProperty,i=Object.getOwnPropertyDescriptor,r=Object.getOwnPropertyNames,s=Object.prototype.hasOwnProperty,n=(e,i)=>{for(var r in i)t(e,r,{get:i[r],enumerable:!0})},a={};n(a,{examples:()=>c,hints:()=>v,schemas:()=>o}),module.exports=(e=a,((e,n,a,o)=>{if(n&&"object"==typeof n||"function"==typeof n)for(let p of r(n))s.call(e,p)||p===a||t(e,p,{get:()=>n[p],enumerable:!(o=i(n,p))||o.enumerable});return e})(t({},"__esModule",{value:!0}),e));var o={};n(o,{SettingsSchema:()=>d,settings:()=>l});var p=require("@walkeros/core/dev"),g=require("@walkeros/core/dev"),d=g.z.object({fields:g.z.array(g.z.union([g.z.string().describe('Dot-notation path: "ingest.ip", "event.data.userId"'),g.z.object({key:g.z.string().optional().describe("Source property path"),value:g.z.unknown().optional().describe("Static value or fallback"),fn:g.z.string().optional().describe("$code: function for value transformation")}).describe("Mapping value config for computed fields")])).describe("Fields to include in hash (order matters). Each resolved via getMappingValue with source { event, ingest }."),output:g.z.string().optional().describe('Dot-notation path where hash is stored on the event. Default: "user.hash"'),length:g.z.number().int().positive().optional().describe("Truncate hash to this length. Default: full 64-char SHA-256 hash")}).describe("Fingerprint transformer: generates deterministic user hashes from event fields"),l=(0,p.zodToSchema)(d),c={};n(c,{step:()=>u});var u={};n(u,{ipAnonymization:()=>h,missingFields:()=>f,serverFingerprint:()=>m});var m={title:"Server fingerprint",description:"Standard server fingerprint using ingest.ip and ingest.userAgent. Requires source config.ingest.",in:{name:"page view",data:{domain:"www.example.com",title:"Getting Started",id:"/docs/getting-started"},id:"ev-1700000600",trigger:"load",entity:"page",action:"view",timestamp:1700000600,source:{type:"express",platform:"server"}},out:[["return",{event:{name:"page view",data:{domain:"www.example.com",title:"Getting Started",id:"/docs/getting-started"},user:{hash:"158f99cc06e33fd6"},id:"ev-1700000600",trigger:"load",entity:"page",action:"view",timestamp:1700000600,source:{type:"express",platform:"server"}}}]]},f={public:!1,description:"Graceful handling when ingest is missing - fields resolve to empty strings, hash is still generated.",in:{name:"session start",data:{id:"s3ss10n"},id:"ev-1700000601",trigger:"load",entity:"session",action:"start",timestamp:1700000601,source:{type:"express",platform:"server"}},out:[["return",{event:{name:"session start",data:{id:"s3ss10n"},user:{hash:"e183220b699c10a8"},id:"ev-1700000601",trigger:"load",entity:"session",action:"start",timestamp:1700000601,source:{type:"express",platform:"server"}}}]]},h={title:"IP anonymization",description:'Privacy-preserving fingerprint using key+fn pattern: fn truncates IP to /24 subnet before hashing, so 10.0.42.* users share a hash. Config: fields: [{ key: "ingest.ip", fn: ip => ip.replace(/\\.\\d+$/, ".0") }, "ingest.userAgent"]',in:{name:"page view",data:{domain:"www.example.com",title:"Privacy Policy",id:"/privacy"},id:"ev-1700000602",trigger:"load",entity:"page",action:"view",timestamp:1700000602,source:{type:"express",platform:"server"}},out:[["return",{event:{name:"page view",data:{domain:"www.example.com",title:"Privacy Policy",id:"/privacy"},user:{hash:"44d9154b9a9b3792"},id:"ev-1700000602",trigger:"load",entity:"page",action:"view",timestamp:1700000602,source:{type:"express",platform:"server"}}}]]},v={"ingest-prerequisite":{text:"Fields starting with ingest.* require the server source to have config.ingest configured. Without it, ingest is undefined and all ingest.* fields resolve to empty strings — the hash is still generated but not unique. Always pair this transformer with a source that extracts request metadata.",code:[{lang:"json",code:JSON.stringify({sources:{express:{package:"@walkeros/server-source-express",config:{settings:{port:8080},ingest:{ip:"req.ip",userAgent:"req.headers.user-agent",origin:"req.headers.origin"}}}},transformers:{fingerprint:{package:"@walkeros/server-transformer-fingerprint",config:{settings:{fields:["ingest.ip","ingest.userAgent"],output:"user.hash",length:16}}}}},null,2)}]},"fields-overview":{text:"Fields resolve from { event, ingest } via walkerOS mapping. Common patterns: ingest.ip (client IP), ingest.userAgent (browser UA), event.data.* (any event property). For time-based rotation use fn fields: daily rotation with toISOString().slice(0,10), monthly with getDate(). Order matters — same fields in different order produce different hashes. Use { key, fn } objects to transform before hashing (e.g., IP anonymization via the ipAnonymization step example)."}};//# sourceMappingURL=dev.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/dev.ts","../src/schemas/index.ts","../src/schemas/settings.ts","../src/examples/index.ts","../src/examples/step.ts","../src/hints.ts"],"sourcesContent":["export * as schemas from './schemas';\nexport * as examples from './examples';\nexport { hints } from './hints';\n","import { zodToSchema } from '@walkeros/core/dev';\nimport { SettingsSchema } from './settings';\n\nexport { SettingsSchema, type Settings } from './settings';\nexport const settings = zodToSchema(SettingsSchema);\n","import { z } from '@walkeros/core/dev';\n\n/**\n * Fingerprint transformer settings schema.\n *\n * Mirrors: types.ts FingerprintSettings\n */\nexport const SettingsSchema = z\n .object({\n fields: z\n .array(\n z.union([\n z\n .string()\n .describe('Dot-notation path: \"ingest.ip\", \"event.data.userId\"'),\n z\n .object({\n key: z.string().optional().describe('Source property path'),\n value: z\n .unknown()\n .optional()\n .describe('Static value or fallback'),\n fn: z\n .string()\n .optional()\n .describe('$code: function for value transformation'),\n })\n .describe('Mapping value config for computed fields'),\n ]),\n )\n .describe(\n 'Fields to include in hash (order matters). Each resolved via getMappingValue with source { event, ingest }.',\n ),\n output: z\n .string()\n .optional()\n .describe(\n 'Dot-notation path where hash is stored on the event. Default: \"user.hash\"',\n ),\n length: z\n .number()\n .int()\n .positive()\n .optional()\n .describe(\n 'Truncate hash to this length. Default: full 64-char SHA-256 hash',\n ),\n })\n .describe(\n 'Fingerprint transformer: generates deterministic user hashes from event fields',\n );\n\nexport type Settings = z.infer<typeof SettingsSchema>;\n","export * as step from './step';\n","import type { Flow } from '@walkeros/core';\n\nexport const serverFingerprint: Flow.StepExample = {\n title: 'Server fingerprint',\n description:\n 'Standard server fingerprint using ingest.ip and ingest.userAgent. Requires source config.ingest.',\n in: {\n name: 'page view',\n data: {\n domain: 'www.example.com',\n title: 'Getting Started',\n id: '/docs/getting-started',\n },\n id: 'ev-1700000600',\n trigger: 'load',\n entity: 'page',\n action: 'view',\n timestamp: 1700000600,\n source: { type: 'express', platform: 'server' },\n },\n out: [\n [\n 'return',\n {\n event: {\n name: 'page view',\n data: {\n domain: 'www.example.com',\n title: 'Getting Started',\n id: '/docs/getting-started',\n },\n user: { hash: '158f99cc06e33fd6' },\n id: 'ev-1700000600',\n trigger: 'load',\n entity: 'page',\n action: 'view',\n timestamp: 1700000600,\n source: { type: 'express', platform: 'server' },\n },\n },\n ],\n ],\n};\n\nexport const missingFields: Flow.StepExample = {\n public: false,\n description:\n 'Graceful handling when ingest is missing - fields resolve to empty strings, hash is still generated.',\n in: {\n name: 'session start',\n data: { id: 's3ss10n' },\n id: 'ev-1700000601',\n trigger: 'load',\n entity: 'session',\n action: 'start',\n timestamp: 1700000601,\n source: { type: 'express', platform: 'server' },\n },\n out: [\n [\n 'return',\n {\n event: {\n name: 'session start',\n data: { id: 's3ss10n' },\n user: { hash: 'e183220b699c10a8' },\n id: 'ev-1700000601',\n trigger: 'load',\n entity: 'session',\n action: 'start',\n timestamp: 1700000601,\n source: { type: 'express', platform: 'server' },\n },\n },\n ],\n ],\n};\n\nexport const ipAnonymization: Flow.StepExample = {\n title: 'IP anonymization',\n description:\n 'Privacy-preserving fingerprint using key+fn pattern: ' +\n 'fn truncates IP to /24 subnet before hashing, so 10.0.42.* users share a hash. ' +\n 'Config: fields: [{ key: \"ingest.ip\", fn: ip => ip.replace(/\\\\.\\\\d+$/, \".0\") }, \"ingest.userAgent\"]',\n in: {\n name: 'page view',\n data: {\n domain: 'www.example.com',\n title: 'Privacy Policy',\n id: '/privacy',\n },\n id: 'ev-1700000602',\n trigger: 'load',\n entity: 'page',\n action: 'view',\n timestamp: 1700000602,\n source: { type: 'express', platform: 'server' },\n },\n out: [\n [\n 'return',\n {\n event: {\n name: 'page view',\n data: {\n domain: 'www.example.com',\n title: 'Privacy Policy',\n id: '/privacy',\n },\n user: { hash: '44d9154b9a9b3792' },\n id: 'ev-1700000602',\n trigger: 'load',\n entity: 'page',\n action: 'view',\n timestamp: 1700000602,\n source: { type: 'express', platform: 'server' },\n },\n },\n ],\n ],\n};\n","import type { Hint } from '@walkeros/core';\n\nexport const hints: Hint.Hints = {\n 'ingest-prerequisite': {\n text: 'Fields starting with ingest.* require the server source to have config.ingest configured. Without it, ingest is undefined and all ingest.* fields resolve to empty strings — the hash is still generated but not unique. Always pair this transformer with a source that extracts request metadata.',\n code: [\n {\n lang: 'json',\n code: JSON.stringify(\n {\n sources: {\n express: {\n package: '@walkeros/server-source-express',\n config: {\n settings: { port: 8080 },\n ingest: {\n ip: 'req.ip',\n userAgent: 'req.headers.user-agent',\n origin: 'req.headers.origin',\n },\n },\n },\n },\n transformers: {\n fingerprint: {\n package: '@walkeros/server-transformer-fingerprint',\n config: {\n settings: {\n fields: ['ingest.ip', 'ingest.userAgent'],\n output: 'user.hash',\n length: 16,\n },\n },\n },\n },\n },\n null,\n 2,\n ),\n },\n ],\n },\n 'fields-overview': {\n text: 'Fields resolve from { event, ingest } via walkerOS mapping. Common patterns: ingest.ip (client IP), ingest.userAgent (browser UA), event.data.* (any event property). For time-based rotation use fn fields: daily rotation with toISOString().slice(0,10), monthly with getDate(). Order matters — same fields in different order produce different hashes. Use { key, fn } objects to transform before hashing (e.g., IP anonymization via the ipAnonymization step example).',\n },\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAAAA,cAA4B;;;ACA5B,iBAAkB;AAOX,IAAM,iBAAiB,aAC3B,OAAO;AAAA,EACN,QAAQ,aACL;AAAA,IACC,aAAE,MAAM;AAAA,MACN,aACG,OAAO,EACP,SAAS,qDAAqD;AAAA,MACjE,aACG,OAAO;AAAA,QACN,KAAK,aAAE,OAAO,EAAE,SAAS,EAAE,SAAS,sBAAsB;AAAA,QAC1D,OAAO,aACJ,QAAQ,EACR,SAAS,EACT,SAAS,0BAA0B;AAAA,QACtC,IAAI,aACD,OAAO,EACP,SAAS,EACT,SAAS,0CAA0C;AAAA,MACxD,CAAC,EACA,SAAS,0CAA0C;AAAA,IACxD,CAAC;AAAA,EACH,EACC;AAAA,IACC;AAAA,EACF;AAAA,EACF,QAAQ,aACL,OAAO,EACP,SAAS,EACT;AAAA,IACC;AAAA,EACF;AAAA,EACF,QAAQ,aACL,OAAO,EACP,IAAI,EACJ,SAAS,EACT,SAAS,EACT;AAAA,IACC;AAAA,EACF;AACJ,CAAC,EACA;AAAA,EACC;AACF;;;AD9CK,IAAM,eAAW,yBAAY,cAAc;;;AEJlD;AAAA;AAAA;AAAA;;;ACAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAEO,IAAM,oBAAsC;AAAA,EACjD,OAAO;AAAA,EACP,aACE;AAAA,EACF,IAAI;AAAA,IACF,MAAM;AAAA,IACN,MAAM;AAAA,MACJ,QAAQ;AAAA,MACR,OAAO;AAAA,MACP,IAAI;AAAA,IACN;AAAA,IACA,IAAI;AAAA,IACJ,SAAS;AAAA,IACT,QAAQ;AAAA,IACR,QAAQ;AAAA,IACR,WAAW;AAAA,IACX,QAAQ,EAAE,MAAM,WAAW,UAAU,SAAS;AAAA,EAChD;AAAA,EACA,KAAK;AAAA,IACH;AAAA,MACE;AAAA,MACA;AAAA,QACE,OAAO;AAAA,UACL,MAAM;AAAA,UACN,MAAM;AAAA,YACJ,QAAQ;AAAA,YACR,OAAO;AAAA,YACP,IAAI;AAAA,UACN;AAAA,UACA,MAAM,EAAE,MAAM,mBAAmB;AAAA,UACjC,IAAI;AAAA,UACJ,SAAS;AAAA,UACT,QAAQ;AAAA,UACR,QAAQ;AAAA,UACR,WAAW;AAAA,UACX,QAAQ,EAAE,MAAM,WAAW,UAAU,SAAS;AAAA,QAChD;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF;AAEO,IAAM,gBAAkC;AAAA,EAC7C,QAAQ;AAAA,EACR,aACE;AAAA,EACF,IAAI;AAAA,IACF,MAAM;AAAA,IACN,MAAM,EAAE,IAAI,UAAU;AAAA,IACtB,IAAI;AAAA,IACJ,SAAS;AAAA,IACT,QAAQ;AAAA,IACR,QAAQ;AAAA,IACR,WAAW;AAAA,IACX,QAAQ,EAAE,MAAM,WAAW,UAAU,SAAS;AAAA,EAChD;AAAA,EACA,KAAK;AAAA,IACH;AAAA,MACE;AAAA,MACA;AAAA,QACE,OAAO;AAAA,UACL,MAAM;AAAA,UACN,MAAM,EAAE,IAAI,UAAU;AAAA,UACtB,MAAM,EAAE,MAAM,mBAAmB;AAAA,UACjC,IAAI;AAAA,UACJ,SAAS;AAAA,UACT,QAAQ;AAAA,UACR,QAAQ;AAAA,UACR,WAAW;AAAA,UACX,QAAQ,EAAE,MAAM,WAAW,UAAU,SAAS;AAAA,QAChD;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF;AAEO,IAAM,kBAAoC;AAAA,EAC/C,OAAO;AAAA,EACP,aACE;AAAA,EAGF,IAAI;AAAA,IACF,MAAM;AAAA,IACN,MAAM;AAAA,MACJ,QAAQ;AAAA,MACR,OAAO;AAAA,MACP,IAAI;AAAA,IACN;AAAA,IACA,IAAI;AAAA,IACJ,SAAS;AAAA,IACT,QAAQ;AAAA,IACR,QAAQ;AAAA,IACR,WAAW;AAAA,IACX,QAAQ,EAAE,MAAM,WAAW,UAAU,SAAS;AAAA,EAChD;AAAA,EACA,KAAK;AAAA,IACH;AAAA,MACE;AAAA,MACA;AAAA,QACE,OAAO;AAAA,UACL,MAAM;AAAA,UACN,MAAM;AAAA,YACJ,QAAQ;AAAA,YACR,OAAO;AAAA,YACP,IAAI;AAAA,UACN;AAAA,UACA,MAAM,EAAE,MAAM,mBAAmB;AAAA,UACjC,IAAI;AAAA,UACJ,SAAS;AAAA,UACT,QAAQ;AAAA,UACR,QAAQ;AAAA,UACR,WAAW;AAAA,UACX,QAAQ,EAAE,MAAM,WAAW,UAAU,SAAS;AAAA,QAChD;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF;;;ACtHO,IAAM,QAAoB;AAAA,EAC/B,uBAAuB;AAAA,IACrB,MAAM;AAAA,IACN,MAAM;AAAA,MACJ;AAAA,QACE,MAAM;AAAA,QACN,MAAM,KAAK;AAAA,UACT;AAAA,YACE,SAAS;AAAA,cACP,SAAS;AAAA,gBACP,SAAS;AAAA,gBACT,QAAQ;AAAA,kBACN,UAAU,EAAE,MAAM,KAAK;AAAA,kBACvB,QAAQ;AAAA,oBACN,IAAI;AAAA,oBACJ,WAAW;AAAA,oBACX,QAAQ;AAAA,kBACV;AAAA,gBACF;AAAA,cACF;AAAA,YACF;AAAA,YACA,cAAc;AAAA,cACZ,aAAa;AAAA,gBACX,SAAS;AAAA,gBACT,QAAQ;AAAA,kBACN,UAAU;AAAA,oBACR,QAAQ,CAAC,aAAa,kBAAkB;AAAA,oBACxC,QAAQ;AAAA,oBACR,QAAQ;AAAA,kBACV;AAAA,gBACF;AAAA,cACF;AAAA,YACF;AAAA,UACF;AAAA,UACA;AAAA,UACA;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAAA,EACA,mBAAmB;AAAA,IACjB,MAAM;AAAA,EACR;AACF;","names":["import_dev"]}
package/dist/dev.mjs ADDED
@@ -0,0 +1 @@
1
+ var e=Object.defineProperty,t=(t,i)=>{for(var r in i)e(t,r,{get:i[r],enumerable:!0})},i={};t(i,{SettingsSchema:()=>n,settings:()=>a});import{zodToSchema as r}from"@walkeros/core/dev";import{z as s}from"@walkeros/core/dev";var n=s.object({fields:s.array(s.union([s.string().describe('Dot-notation path: "ingest.ip", "event.data.userId"'),s.object({key:s.string().optional().describe("Source property path"),value:s.unknown().optional().describe("Static value or fallback"),fn:s.string().optional().describe("$code: function for value transformation")}).describe("Mapping value config for computed fields")])).describe("Fields to include in hash (order matters). Each resolved via getMappingValue with source { event, ingest }."),output:s.string().optional().describe('Dot-notation path where hash is stored on the event. Default: "user.hash"'),length:s.number().int().positive().optional().describe("Truncate hash to this length. Default: full 64-char SHA-256 hash")}).describe("Fingerprint transformer: generates deterministic user hashes from event fields"),a=r(n),o={};t(o,{step:()=>g});var g={};t(g,{ipAnonymization:()=>l,missingFields:()=>d,serverFingerprint:()=>p});var p={title:"Server fingerprint",description:"Standard server fingerprint using ingest.ip and ingest.userAgent. Requires source config.ingest.",in:{name:"page view",data:{domain:"www.example.com",title:"Getting Started",id:"/docs/getting-started"},id:"ev-1700000600",trigger:"load",entity:"page",action:"view",timestamp:1700000600,source:{type:"express",platform:"server"}},out:[["return",{event:{name:"page view",data:{domain:"www.example.com",title:"Getting Started",id:"/docs/getting-started"},user:{hash:"158f99cc06e33fd6"},id:"ev-1700000600",trigger:"load",entity:"page",action:"view",timestamp:1700000600,source:{type:"express",platform:"server"}}}]]},d={public:!1,description:"Graceful handling when ingest is missing - fields resolve to empty strings, hash is still generated.",in:{name:"session start",data:{id:"s3ss10n"},id:"ev-1700000601",trigger:"load",entity:"session",action:"start",timestamp:1700000601,source:{type:"express",platform:"server"}},out:[["return",{event:{name:"session start",data:{id:"s3ss10n"},user:{hash:"e183220b699c10a8"},id:"ev-1700000601",trigger:"load",entity:"session",action:"start",timestamp:1700000601,source:{type:"express",platform:"server"}}}]]},l={title:"IP anonymization",description:'Privacy-preserving fingerprint using key+fn pattern: fn truncates IP to /24 subnet before hashing, so 10.0.42.* users share a hash. Config: fields: [{ key: "ingest.ip", fn: ip => ip.replace(/\\.\\d+$/, ".0") }, "ingest.userAgent"]',in:{name:"page view",data:{domain:"www.example.com",title:"Privacy Policy",id:"/privacy"},id:"ev-1700000602",trigger:"load",entity:"page",action:"view",timestamp:1700000602,source:{type:"express",platform:"server"}},out:[["return",{event:{name:"page view",data:{domain:"www.example.com",title:"Privacy Policy",id:"/privacy"},user:{hash:"44d9154b9a9b3792"},id:"ev-1700000602",trigger:"load",entity:"page",action:"view",timestamp:1700000602,source:{type:"express",platform:"server"}}}]]},c={"ingest-prerequisite":{text:"Fields starting with ingest.* require the server source to have config.ingest configured. Without it, ingest is undefined and all ingest.* fields resolve to empty strings — the hash is still generated but not unique. Always pair this transformer with a source that extracts request metadata.",code:[{lang:"json",code:JSON.stringify({sources:{express:{package:"@walkeros/server-source-express",config:{settings:{port:8080},ingest:{ip:"req.ip",userAgent:"req.headers.user-agent",origin:"req.headers.origin"}}}},transformers:{fingerprint:{package:"@walkeros/server-transformer-fingerprint",config:{settings:{fields:["ingest.ip","ingest.userAgent"],output:"user.hash",length:16}}}}},null,2)}]},"fields-overview":{text:"Fields resolve from { event, ingest } via walkerOS mapping. Common patterns: ingest.ip (client IP), ingest.userAgent (browser UA), event.data.* (any event property). For time-based rotation use fn fields: daily rotation with toISOString().slice(0,10), monthly with getDate(). Order matters — same fields in different order produce different hashes. Use { key, fn } objects to transform before hashing (e.g., IP anonymization via the ipAnonymization step example)."}};export{o as examples,c as hints,i as schemas};//# sourceMappingURL=dev.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/schemas/index.ts","../src/schemas/settings.ts","../src/examples/index.ts","../src/examples/step.ts","../src/hints.ts"],"sourcesContent":["import { zodToSchema } from '@walkeros/core/dev';\nimport { SettingsSchema } from './settings';\n\nexport { SettingsSchema, type Settings } from './settings';\nexport const settings = zodToSchema(SettingsSchema);\n","import { z } from '@walkeros/core/dev';\n\n/**\n * Fingerprint transformer settings schema.\n *\n * Mirrors: types.ts FingerprintSettings\n */\nexport const SettingsSchema = z\n .object({\n fields: z\n .array(\n z.union([\n z\n .string()\n .describe('Dot-notation path: \"ingest.ip\", \"event.data.userId\"'),\n z\n .object({\n key: z.string().optional().describe('Source property path'),\n value: z\n .unknown()\n .optional()\n .describe('Static value or fallback'),\n fn: z\n .string()\n .optional()\n .describe('$code: function for value transformation'),\n })\n .describe('Mapping value config for computed fields'),\n ]),\n )\n .describe(\n 'Fields to include in hash (order matters). Each resolved via getMappingValue with source { event, ingest }.',\n ),\n output: z\n .string()\n .optional()\n .describe(\n 'Dot-notation path where hash is stored on the event. Default: \"user.hash\"',\n ),\n length: z\n .number()\n .int()\n .positive()\n .optional()\n .describe(\n 'Truncate hash to this length. Default: full 64-char SHA-256 hash',\n ),\n })\n .describe(\n 'Fingerprint transformer: generates deterministic user hashes from event fields',\n );\n\nexport type Settings = z.infer<typeof SettingsSchema>;\n","export * as step from './step';\n","import type { Flow } from '@walkeros/core';\n\nexport const serverFingerprint: Flow.StepExample = {\n title: 'Server fingerprint',\n description:\n 'Standard server fingerprint using ingest.ip and ingest.userAgent. Requires source config.ingest.',\n in: {\n name: 'page view',\n data: {\n domain: 'www.example.com',\n title: 'Getting Started',\n id: '/docs/getting-started',\n },\n id: 'ev-1700000600',\n trigger: 'load',\n entity: 'page',\n action: 'view',\n timestamp: 1700000600,\n source: { type: 'express', platform: 'server' },\n },\n out: [\n [\n 'return',\n {\n event: {\n name: 'page view',\n data: {\n domain: 'www.example.com',\n title: 'Getting Started',\n id: '/docs/getting-started',\n },\n user: { hash: '158f99cc06e33fd6' },\n id: 'ev-1700000600',\n trigger: 'load',\n entity: 'page',\n action: 'view',\n timestamp: 1700000600,\n source: { type: 'express', platform: 'server' },\n },\n },\n ],\n ],\n};\n\nexport const missingFields: Flow.StepExample = {\n public: false,\n description:\n 'Graceful handling when ingest is missing - fields resolve to empty strings, hash is still generated.',\n in: {\n name: 'session start',\n data: { id: 's3ss10n' },\n id: 'ev-1700000601',\n trigger: 'load',\n entity: 'session',\n action: 'start',\n timestamp: 1700000601,\n source: { type: 'express', platform: 'server' },\n },\n out: [\n [\n 'return',\n {\n event: {\n name: 'session start',\n data: { id: 's3ss10n' },\n user: { hash: 'e183220b699c10a8' },\n id: 'ev-1700000601',\n trigger: 'load',\n entity: 'session',\n action: 'start',\n timestamp: 1700000601,\n source: { type: 'express', platform: 'server' },\n },\n },\n ],\n ],\n};\n\nexport const ipAnonymization: Flow.StepExample = {\n title: 'IP anonymization',\n description:\n 'Privacy-preserving fingerprint using key+fn pattern: ' +\n 'fn truncates IP to /24 subnet before hashing, so 10.0.42.* users share a hash. ' +\n 'Config: fields: [{ key: \"ingest.ip\", fn: ip => ip.replace(/\\\\.\\\\d+$/, \".0\") }, \"ingest.userAgent\"]',\n in: {\n name: 'page view',\n data: {\n domain: 'www.example.com',\n title: 'Privacy Policy',\n id: '/privacy',\n },\n id: 'ev-1700000602',\n trigger: 'load',\n entity: 'page',\n action: 'view',\n timestamp: 1700000602,\n source: { type: 'express', platform: 'server' },\n },\n out: [\n [\n 'return',\n {\n event: {\n name: 'page view',\n data: {\n domain: 'www.example.com',\n title: 'Privacy Policy',\n id: '/privacy',\n },\n user: { hash: '44d9154b9a9b3792' },\n id: 'ev-1700000602',\n trigger: 'load',\n entity: 'page',\n action: 'view',\n timestamp: 1700000602,\n source: { type: 'express', platform: 'server' },\n },\n },\n ],\n ],\n};\n","import type { Hint } from '@walkeros/core';\n\nexport const hints: Hint.Hints = {\n 'ingest-prerequisite': {\n text: 'Fields starting with ingest.* require the server source to have config.ingest configured. Without it, ingest is undefined and all ingest.* fields resolve to empty strings — the hash is still generated but not unique. Always pair this transformer with a source that extracts request metadata.',\n code: [\n {\n lang: 'json',\n code: JSON.stringify(\n {\n sources: {\n express: {\n package: '@walkeros/server-source-express',\n config: {\n settings: { port: 8080 },\n ingest: {\n ip: 'req.ip',\n userAgent: 'req.headers.user-agent',\n origin: 'req.headers.origin',\n },\n },\n },\n },\n transformers: {\n fingerprint: {\n package: '@walkeros/server-transformer-fingerprint',\n config: {\n settings: {\n fields: ['ingest.ip', 'ingest.userAgent'],\n output: 'user.hash',\n length: 16,\n },\n },\n },\n },\n },\n null,\n 2,\n ),\n },\n ],\n },\n 'fields-overview': {\n text: 'Fields resolve from { event, ingest } via walkerOS mapping. Common patterns: ingest.ip (client IP), ingest.userAgent (browser UA), event.data.* (any event property). For time-based rotation use fn fields: daily rotation with toISOString().slice(0,10), monthly with getDate(). Order matters — same fields in different order produce different hashes. Use { key, fn } objects to transform before hashing (e.g., IP anonymization via the ipAnonymization step example).',\n },\n};\n"],"mappings":"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","names":[]}
package/dist/index.d.mts CHANGED
@@ -53,4 +53,4 @@ interface FingerprintSettings {
53
53
  */
54
54
  declare const transformerFingerprint: Transformer.Init<Transformer.Types<FingerprintSettings>>;
55
55
 
56
- export { type FingerprintSettings, transformerFingerprint };
56
+ export { type FingerprintSettings, transformerFingerprint as default, transformerFingerprint };
package/dist/index.d.ts CHANGED
@@ -53,4 +53,4 @@ interface FingerprintSettings {
53
53
  */
54
54
  declare const transformerFingerprint: Transformer.Init<Transformer.Types<FingerprintSettings>>;
55
55
 
56
- export { type FingerprintSettings, transformerFingerprint };
56
+ export { type FingerprintSettings, transformerFingerprint as default, transformerFingerprint };
package/dist/index.js CHANGED
@@ -1 +1 @@
1
- "use strict";var e,r=Object.defineProperty,t=Object.getOwnPropertyDescriptor,o=Object.getOwnPropertyNames,n=Object.prototype.hasOwnProperty,a={};((e,t)=>{for(var o in t)r(e,o,{get:t[o],enumerable:!0})})(a,{transformerFingerprint:()=>l}),module.exports=(e=a,((e,a,s,i)=>{if(a&&"object"==typeof a||"function"==typeof a)for(let l of o(a))n.call(e,l)||l===s||r(e,l,{get:()=>a[l],enumerable:!(i=t(a,l))||i.enumerable});return e})(r({},"__esModule",{value:!0}),e));var s=require("@walkeros/core"),i=require("@walkeros/server-core"),l=e=>{const{config:r}=e,t=r.settings||{},o=t.fields||[],n=t.output||"user.hash",a=t.length;return{type:"fingerprint",config:r,async push(e,r){const{ingest:t,collector:l}=r,c={event:e,ingest:t},p=""+(await Promise.all(o.map(e=>(0,s.getMappingValue)(c,e,{collector:l})))).map(e=>String(null!=e?e:"")).join(""),u=await(0,i.getHashServer)(p,a);return(0,s.setByPath)(e,n,u)}}};//# sourceMappingURL=index.js.map
1
+ "use strict";var e,r=Object.defineProperty,t=Object.getOwnPropertyDescriptor,o=Object.getOwnPropertyNames,n=Object.prototype.hasOwnProperty,a={};((e,t)=>{for(var o in t)r(e,o,{get:t[o],enumerable:!0})})(a,{default:()=>l,transformerFingerprint:()=>l}),module.exports=(e=a,((e,a,s,i)=>{if(a&&"object"==typeof a||"function"==typeof a)for(let l of o(a))n.call(e,l)||l===s||r(e,l,{get:()=>a[l],enumerable:!(i=t(a,l))||i.enumerable});return e})(r({},"__esModule",{value:!0}),e));var s=require("@walkeros/core"),i=require("@walkeros/server-core"),l=e=>{const{config:r}=e,t=r.settings||{},o=t.fields||[],n=t.output||"user.hash",a=t.length;return{type:"fingerprint",config:r,async push(e,r){const{ingest:t,collector:l}=r,c={event:e,ingest:t},p=""+(await Promise.all(o.map(e=>(0,s.getMappingValue)(c,e,{collector:l})))).map(e=>String(e??"")).join(""),u=await(0,i.getHashServer)(p,a);return{event:(0,s.setByPath)(e,n,u)}}}};//# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/transformer.ts"],"sourcesContent":["export { transformerFingerprint } from './transformer';\nexport type { FingerprintSettings } from './types';\n","import type { Mapping, Transformer } from '@walkeros/core';\nimport { getMappingValue, setByPath } from '@walkeros/core';\nimport { getHashServer } from '@walkeros/server-core';\nimport type { FingerprintSettings } from './types';\n\n/**\n * Fingerprint transformer - hash configurable fields for session continuity.\n *\n * Resolves fields from { event, ingest } source object using getMappingValue,\n * concatenates values in order, hashes with SHA-256, and stores at output path.\n *\n * @example\n * transformerFingerprint({\n * config: {\n * settings: {\n * fields: [\n * 'ingest.ip',\n * 'ingest.userAgent',\n * { fn: () => new Date().getDate() },\n * ],\n * output: 'user.hash',\n * length: 16,\n * },\n * },\n * })\n */\nexport const transformerFingerprint: Transformer.Init<\n Transformer.Types<FingerprintSettings>\n> = (context) => {\n const { config } = context;\n const settings = (config.settings || {}) as Partial<FingerprintSettings>;\n const fields: Mapping.Value[] = settings.fields || [];\n const output: string = settings.output || 'user.hash';\n const length: number | undefined = settings.length;\n\n return {\n type: 'fingerprint',\n config: config as Transformer.Config<\n Transformer.Types<FingerprintSettings>\n >,\n\n async push(event, context) {\n const { ingest, collector } = context;\n\n // Build source object for field resolution\n const source = { event, ingest };\n\n // Resolve each field via mapping (maintains order)\n const values = await Promise.all(\n fields.map((field: Mapping.Value) =>\n getMappingValue(source, field, { collector }),\n ),\n );\n\n // Safe string concatenation: '' prefix + String() cast for each value\n // '' prefix ensures we always have a string even if fields is empty\n // String(v ?? '') handles undefined/null gracefully\n const input = '' + values.map((v: unknown) => String(v ?? '')).join('');\n\n // Hash and store at output path\n const hash = await getHashServer(input, length);\n return setByPath(event, output, hash);\n },\n };\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACCA,kBAA2C;AAC3C,yBAA8B;AAwBvB,IAAM,yBAET,CAAC,YAAY;AACf,QAAM,EAAE,OAAO,IAAI;AACnB,QAAM,WAAY,OAAO,YAAY,CAAC;AACtC,QAAM,SAA0B,SAAS,UAAU,CAAC;AACpD,QAAM,SAAiB,SAAS,UAAU;AAC1C,QAAM,SAA6B,SAAS;AAE5C,SAAO;AAAA,IACL,MAAM;AAAA,IACN;AAAA,IAIA,MAAM,KAAK,OAAOA,UAAS;AACzB,YAAM,EAAE,QAAQ,UAAU,IAAIA;AAG9B,YAAM,SAAS,EAAE,OAAO,OAAO;AAG/B,YAAM,SAAS,MAAM,QAAQ;AAAA,QAC3B,OAAO;AAAA,UAAI,CAAC,cACV,6BAAgB,QAAQ,OAAO,EAAE,UAAU,CAAC;AAAA,QAC9C;AAAA,MACF;AAKA,YAAM,QAAQ,KAAK,OAAO,IAAI,CAAC,MAAe,OAAO,gBAAK,EAAE,CAAC,EAAE,KAAK,EAAE;AAGtE,YAAM,OAAO,UAAM,kCAAc,OAAO,MAAM;AAC9C,iBAAO,uBAAU,OAAO,QAAQ,IAAI;AAAA,IACtC;AAAA,EACF;AACF;","names":["context"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/transformer.ts"],"sourcesContent":["export { transformerFingerprint } from './transformer';\nexport type { FingerprintSettings } from './types';\n\nexport { transformerFingerprint as default } from './transformer';\n","import type { Mapping, Transformer } from '@walkeros/core';\nimport { getMappingValue, setByPath } from '@walkeros/core';\nimport { getHashServer } from '@walkeros/server-core';\nimport type { FingerprintSettings } from './types';\n\n/**\n * Fingerprint transformer - hash configurable fields for session continuity.\n *\n * Resolves fields from { event, ingest } source object using getMappingValue,\n * concatenates values in order, hashes with SHA-256, and stores at output path.\n *\n * @example\n * transformerFingerprint({\n * config: {\n * settings: {\n * fields: [\n * 'ingest.ip',\n * 'ingest.userAgent',\n * { fn: () => new Date().getDate() },\n * ],\n * output: 'user.hash',\n * length: 16,\n * },\n * },\n * })\n */\nexport const transformerFingerprint: Transformer.Init<\n Transformer.Types<FingerprintSettings>\n> = (context) => {\n const { config } = context;\n const settings = (config.settings || {}) as Partial<FingerprintSettings>;\n const fields: Mapping.Value[] = settings.fields || [];\n const output: string = settings.output || 'user.hash';\n const length: number | undefined = settings.length;\n\n return {\n type: 'fingerprint',\n config: config as Transformer.Config<\n Transformer.Types<FingerprintSettings>\n >,\n\n async push(event, context) {\n const { ingest, collector } = context;\n\n // Build source object for field resolution\n const source = { event, ingest };\n\n // Resolve each field via mapping (maintains order)\n const values = await Promise.all(\n fields.map((field: Mapping.Value) =>\n getMappingValue(source, field, { collector }),\n ),\n );\n\n // Safe string concatenation: '' prefix + String() cast for each value\n // '' prefix ensures we always have a string even if fields is empty\n // String(v ?? '') handles undefined/null gracefully\n const input = '' + values.map((v: unknown) => String(v ?? '')).join('');\n\n // Hash and store at output path\n const hash = await getHashServer(input, length);\n return { event: setByPath(event, output, hash) };\n },\n };\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACCA,kBAA2C;AAC3C,yBAA8B;AAwBvB,IAAM,yBAET,CAAC,YAAY;AACf,QAAM,EAAE,OAAO,IAAI;AACnB,QAAM,WAAY,OAAO,YAAY,CAAC;AACtC,QAAM,SAA0B,SAAS,UAAU,CAAC;AACpD,QAAM,SAAiB,SAAS,UAAU;AAC1C,QAAM,SAA6B,SAAS;AAE5C,SAAO;AAAA,IACL,MAAM;AAAA,IACN;AAAA,IAIA,MAAM,KAAK,OAAOA,UAAS;AACzB,YAAM,EAAE,QAAQ,UAAU,IAAIA;AAG9B,YAAM,SAAS,EAAE,OAAO,OAAO;AAG/B,YAAM,SAAS,MAAM,QAAQ;AAAA,QAC3B,OAAO;AAAA,UAAI,CAAC,cACV,6BAAgB,QAAQ,OAAO,EAAE,UAAU,CAAC;AAAA,QAC9C;AAAA,MACF;AAKA,YAAM,QAAQ,KAAK,OAAO,IAAI,CAAC,MAAe,OAAO,KAAK,EAAE,CAAC,EAAE,KAAK,EAAE;AAGtE,YAAM,OAAO,UAAM,kCAAc,OAAO,MAAM;AAC9C,aAAO,EAAE,WAAO,uBAAU,OAAO,QAAQ,IAAI,EAAE;AAAA,IACjD;AAAA,EACF;AACF;","names":["context"]}
package/dist/index.mjs CHANGED
@@ -1 +1 @@
1
- import{getMappingValue as r,setByPath as e}from"@walkeros/core";import{getHashServer as t}from"@walkeros/server-core";var o=o=>{const{config:n}=o,i=n.settings||{},s=i.fields||[],a=i.output||"user.hash",l=i.length;return{type:"fingerprint",config:n,async push(o,n){const{ingest:i,collector:c}=n,p={event:o,ingest:i},g=""+(await Promise.all(s.map(e=>r(p,e,{collector:c})))).map(r=>String(null!=r?r:"")).join(""),m=await t(g,l);return e(o,a,m)}}};export{o as transformerFingerprint};//# sourceMappingURL=index.mjs.map
1
+ import{getMappingValue as e,setByPath as t}from"@walkeros/core";import{getHashServer as r}from"@walkeros/server-core";var o=o=>{const{config:n}=o,s=n.settings||{},i=s.fields||[],a=s.output||"user.hash",c=s.length;return{type:"fingerprint",config:n,async push(o,n){const{ingest:s,collector:l}=n,p={event:o,ingest:s},g=""+(await Promise.all(i.map(t=>e(p,t,{collector:l})))).map(e=>String(e??"")).join(""),f=await r(g,c);return{event:t(o,a,f)}}}};export{o as default,o as transformerFingerprint};//# sourceMappingURL=index.mjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/transformer.ts"],"sourcesContent":["import type { Mapping, Transformer } from '@walkeros/core';\nimport { getMappingValue, setByPath } from '@walkeros/core';\nimport { getHashServer } from '@walkeros/server-core';\nimport type { FingerprintSettings } from './types';\n\n/**\n * Fingerprint transformer - hash configurable fields for session continuity.\n *\n * Resolves fields from { event, ingest } source object using getMappingValue,\n * concatenates values in order, hashes with SHA-256, and stores at output path.\n *\n * @example\n * transformerFingerprint({\n * config: {\n * settings: {\n * fields: [\n * 'ingest.ip',\n * 'ingest.userAgent',\n * { fn: () => new Date().getDate() },\n * ],\n * output: 'user.hash',\n * length: 16,\n * },\n * },\n * })\n */\nexport const transformerFingerprint: Transformer.Init<\n Transformer.Types<FingerprintSettings>\n> = (context) => {\n const { config } = context;\n const settings = (config.settings || {}) as Partial<FingerprintSettings>;\n const fields: Mapping.Value[] = settings.fields || [];\n const output: string = settings.output || 'user.hash';\n const length: number | undefined = settings.length;\n\n return {\n type: 'fingerprint',\n config: config as Transformer.Config<\n Transformer.Types<FingerprintSettings>\n >,\n\n async push(event, context) {\n const { ingest, collector } = context;\n\n // Build source object for field resolution\n const source = { event, ingest };\n\n // Resolve each field via mapping (maintains order)\n const values = await Promise.all(\n fields.map((field: Mapping.Value) =>\n getMappingValue(source, field, { collector }),\n ),\n );\n\n // Safe string concatenation: '' prefix + String() cast for each value\n // '' prefix ensures we always have a string even if fields is empty\n // String(v ?? '') handles undefined/null gracefully\n const input = '' + values.map((v: unknown) => String(v ?? '')).join('');\n\n // Hash and store at output path\n const hash = await getHashServer(input, length);\n return setByPath(event, output, hash);\n },\n };\n};\n"],"mappings":";AACA,SAAS,iBAAiB,iBAAiB;AAC3C,SAAS,qBAAqB;AAwBvB,IAAM,yBAET,CAAC,YAAY;AACf,QAAM,EAAE,OAAO,IAAI;AACnB,QAAM,WAAY,OAAO,YAAY,CAAC;AACtC,QAAM,SAA0B,SAAS,UAAU,CAAC;AACpD,QAAM,SAAiB,SAAS,UAAU;AAC1C,QAAM,SAA6B,SAAS;AAE5C,SAAO;AAAA,IACL,MAAM;AAAA,IACN;AAAA,IAIA,MAAM,KAAK,OAAOA,UAAS;AACzB,YAAM,EAAE,QAAQ,UAAU,IAAIA;AAG9B,YAAM,SAAS,EAAE,OAAO,OAAO;AAG/B,YAAM,SAAS,MAAM,QAAQ;AAAA,QAC3B,OAAO;AAAA,UAAI,CAAC,UACV,gBAAgB,QAAQ,OAAO,EAAE,UAAU,CAAC;AAAA,QAC9C;AAAA,MACF;AAKA,YAAM,QAAQ,KAAK,OAAO,IAAI,CAAC,MAAe,OAAO,gBAAK,EAAE,CAAC,EAAE,KAAK,EAAE;AAGtE,YAAM,OAAO,MAAM,cAAc,OAAO,MAAM;AAC9C,aAAO,UAAU,OAAO,QAAQ,IAAI;AAAA,IACtC;AAAA,EACF;AACF;","names":["context"]}
1
+ {"version":3,"sources":["../src/transformer.ts"],"sourcesContent":["import type { Mapping, Transformer } from '@walkeros/core';\nimport { getMappingValue, setByPath } from '@walkeros/core';\nimport { getHashServer } from '@walkeros/server-core';\nimport type { FingerprintSettings } from './types';\n\n/**\n * Fingerprint transformer - hash configurable fields for session continuity.\n *\n * Resolves fields from { event, ingest } source object using getMappingValue,\n * concatenates values in order, hashes with SHA-256, and stores at output path.\n *\n * @example\n * transformerFingerprint({\n * config: {\n * settings: {\n * fields: [\n * 'ingest.ip',\n * 'ingest.userAgent',\n * { fn: () => new Date().getDate() },\n * ],\n * output: 'user.hash',\n * length: 16,\n * },\n * },\n * })\n */\nexport const transformerFingerprint: Transformer.Init<\n Transformer.Types<FingerprintSettings>\n> = (context) => {\n const { config } = context;\n const settings = (config.settings || {}) as Partial<FingerprintSettings>;\n const fields: Mapping.Value[] = settings.fields || [];\n const output: string = settings.output || 'user.hash';\n const length: number | undefined = settings.length;\n\n return {\n type: 'fingerprint',\n config: config as Transformer.Config<\n Transformer.Types<FingerprintSettings>\n >,\n\n async push(event, context) {\n const { ingest, collector } = context;\n\n // Build source object for field resolution\n const source = { event, ingest };\n\n // Resolve each field via mapping (maintains order)\n const values = await Promise.all(\n fields.map((field: Mapping.Value) =>\n getMappingValue(source, field, { collector }),\n ),\n );\n\n // Safe string concatenation: '' prefix + String() cast for each value\n // '' prefix ensures we always have a string even if fields is empty\n // String(v ?? '') handles undefined/null gracefully\n const input = '' + values.map((v: unknown) => String(v ?? '')).join('');\n\n // Hash and store at output path\n const hash = await getHashServer(input, length);\n return { event: setByPath(event, output, hash) };\n },\n };\n};\n"],"mappings":";AACA,SAAS,iBAAiB,iBAAiB;AAC3C,SAAS,qBAAqB;AAwBvB,IAAM,yBAET,CAAC,YAAY;AACf,QAAM,EAAE,OAAO,IAAI;AACnB,QAAM,WAAY,OAAO,YAAY,CAAC;AACtC,QAAM,SAA0B,SAAS,UAAU,CAAC;AACpD,QAAM,SAAiB,SAAS,UAAU;AAC1C,QAAM,SAA6B,SAAS;AAE5C,SAAO;AAAA,IACL,MAAM;AAAA,IACN;AAAA,IAIA,MAAM,KAAK,OAAOA,UAAS;AACzB,YAAM,EAAE,QAAQ,UAAU,IAAIA;AAG9B,YAAM,SAAS,EAAE,OAAO,OAAO;AAG/B,YAAM,SAAS,MAAM,QAAQ;AAAA,QAC3B,OAAO;AAAA,UAAI,CAAC,UACV,gBAAgB,QAAQ,OAAO,EAAE,UAAU,CAAC;AAAA,QAC9C;AAAA,MACF;AAKA,YAAM,QAAQ,KAAK,OAAO,IAAI,CAAC,MAAe,OAAO,KAAK,EAAE,CAAC,EAAE,KAAK,EAAE;AAGtE,YAAM,OAAO,MAAM,cAAc,OAAO,MAAM;AAC9C,aAAO,EAAE,OAAO,UAAU,OAAO,QAAQ,IAAI,EAAE;AAAA,IACjD;AAAA,EACF;AACF;","names":["context"]}
@@ -0,0 +1,223 @@
1
+ {
2
+ "$meta": {
3
+ "package": "@walkeros/server-transformer-fingerprint",
4
+ "version": "4.0.1-next-1778183328892",
5
+ "type": "transformer",
6
+ "platform": [
7
+ "server"
8
+ ],
9
+ "docs": "https://www.walkeros.io/docs/transformers/fingerprint",
10
+ "source": "https://github.com/elbwalker/walkerOS/tree/main/packages/server/transformers/fingerprint/src"
11
+ },
12
+ "schemas": {
13
+ "settings": {
14
+ "$schema": "http://json-schema.org/draft-07/schema#",
15
+ "type": "object",
16
+ "properties": {
17
+ "fields": {
18
+ "type": "array",
19
+ "items": {
20
+ "anyOf": [
21
+ {
22
+ "type": "string",
23
+ "description": "Dot-notation path: \"ingest.ip\", \"event.data.userId\""
24
+ },
25
+ {
26
+ "type": "object",
27
+ "properties": {
28
+ "key": {
29
+ "description": "Source property path",
30
+ "type": "string"
31
+ },
32
+ "value": {
33
+ "description": "Static value or fallback"
34
+ },
35
+ "fn": {
36
+ "description": "$code: function for value transformation",
37
+ "type": "string"
38
+ }
39
+ },
40
+ "additionalProperties": false,
41
+ "description": "Mapping value config for computed fields"
42
+ }
43
+ ]
44
+ },
45
+ "description": "Fields to include in hash (order matters). Each resolved via getMappingValue with source { event, ingest }."
46
+ },
47
+ "output": {
48
+ "description": "Dot-notation path where hash is stored on the event. Default: \"user.hash\"",
49
+ "type": "string"
50
+ },
51
+ "length": {
52
+ "description": "Truncate hash to this length. Default: full 64-char SHA-256 hash",
53
+ "type": "integer",
54
+ "exclusiveMinimum": 0,
55
+ "maximum": 9007199254740991
56
+ }
57
+ },
58
+ "required": [
59
+ "fields"
60
+ ],
61
+ "additionalProperties": false,
62
+ "description": "Fingerprint transformer: generates deterministic user hashes from event fields"
63
+ }
64
+ },
65
+ "examples": {
66
+ "step": {
67
+ "ipAnonymization": {
68
+ "title": "IP anonymization",
69
+ "description": "Privacy-preserving fingerprint using key+fn pattern: fn truncates IP to /24 subnet before hashing, so 10.0.42.* users share a hash. Config: fields: [{ key: \"ingest.ip\", fn: ip => ip.replace(/\\.\\d+$/, \".0\") }, \"ingest.userAgent\"]",
70
+ "in": {
71
+ "name": "page view",
72
+ "data": {
73
+ "domain": "www.example.com",
74
+ "title": "Privacy Policy",
75
+ "id": "/privacy"
76
+ },
77
+ "id": "ev-1700000602",
78
+ "trigger": "load",
79
+ "entity": "page",
80
+ "action": "view",
81
+ "timestamp": 1700000602,
82
+ "source": {
83
+ "type": "express",
84
+ "platform": "server"
85
+ }
86
+ },
87
+ "out": [
88
+ [
89
+ "return",
90
+ {
91
+ "event": {
92
+ "name": "page view",
93
+ "data": {
94
+ "domain": "www.example.com",
95
+ "title": "Privacy Policy",
96
+ "id": "/privacy"
97
+ },
98
+ "user": {
99
+ "hash": "44d9154b9a9b3792"
100
+ },
101
+ "id": "ev-1700000602",
102
+ "trigger": "load",
103
+ "entity": "page",
104
+ "action": "view",
105
+ "timestamp": 1700000602,
106
+ "source": {
107
+ "type": "express",
108
+ "platform": "server"
109
+ }
110
+ }
111
+ }
112
+ ]
113
+ ]
114
+ },
115
+ "missingFields": {
116
+ "public": false,
117
+ "description": "Graceful handling when ingest is missing - fields resolve to empty strings, hash is still generated.",
118
+ "in": {
119
+ "name": "session start",
120
+ "data": {
121
+ "id": "s3ss10n"
122
+ },
123
+ "id": "ev-1700000601",
124
+ "trigger": "load",
125
+ "entity": "session",
126
+ "action": "start",
127
+ "timestamp": 1700000601,
128
+ "source": {
129
+ "type": "express",
130
+ "platform": "server"
131
+ }
132
+ },
133
+ "out": [
134
+ [
135
+ "return",
136
+ {
137
+ "event": {
138
+ "name": "session start",
139
+ "data": {
140
+ "id": "s3ss10n"
141
+ },
142
+ "user": {
143
+ "hash": "e183220b699c10a8"
144
+ },
145
+ "id": "ev-1700000601",
146
+ "trigger": "load",
147
+ "entity": "session",
148
+ "action": "start",
149
+ "timestamp": 1700000601,
150
+ "source": {
151
+ "type": "express",
152
+ "platform": "server"
153
+ }
154
+ }
155
+ }
156
+ ]
157
+ ]
158
+ },
159
+ "serverFingerprint": {
160
+ "title": "Server fingerprint",
161
+ "description": "Standard server fingerprint using ingest.ip and ingest.userAgent. Requires source config.ingest.",
162
+ "in": {
163
+ "name": "page view",
164
+ "data": {
165
+ "domain": "www.example.com",
166
+ "title": "Getting Started",
167
+ "id": "/docs/getting-started"
168
+ },
169
+ "id": "ev-1700000600",
170
+ "trigger": "load",
171
+ "entity": "page",
172
+ "action": "view",
173
+ "timestamp": 1700000600,
174
+ "source": {
175
+ "type": "express",
176
+ "platform": "server"
177
+ }
178
+ },
179
+ "out": [
180
+ [
181
+ "return",
182
+ {
183
+ "event": {
184
+ "name": "page view",
185
+ "data": {
186
+ "domain": "www.example.com",
187
+ "title": "Getting Started",
188
+ "id": "/docs/getting-started"
189
+ },
190
+ "user": {
191
+ "hash": "158f99cc06e33fd6"
192
+ },
193
+ "id": "ev-1700000600",
194
+ "trigger": "load",
195
+ "entity": "page",
196
+ "action": "view",
197
+ "timestamp": 1700000600,
198
+ "source": {
199
+ "type": "express",
200
+ "platform": "server"
201
+ }
202
+ }
203
+ }
204
+ ]
205
+ ]
206
+ }
207
+ }
208
+ },
209
+ "hints": {
210
+ "ingest-prerequisite": {
211
+ "text": "Fields starting with ingest.* require the server source to have config.ingest configured. Without it, ingest is undefined and all ingest.* fields resolve to empty strings — the hash is still generated but not unique. Always pair this transformer with a source that extracts request metadata.",
212
+ "code": [
213
+ {
214
+ "lang": "json",
215
+ "code": "{\n \"sources\": {\n \"express\": {\n \"package\": \"@walkeros/server-source-express\",\n \"config\": {\n \"settings\": {\n \"port\": 8080\n },\n \"ingest\": {\n \"ip\": \"req.ip\",\n \"userAgent\": \"req.headers.user-agent\",\n \"origin\": \"req.headers.origin\"\n }\n }\n }\n },\n \"transformers\": {\n \"fingerprint\": {\n \"package\": \"@walkeros/server-transformer-fingerprint\",\n \"config\": {\n \"settings\": {\n \"fields\": [\n \"ingest.ip\",\n \"ingest.userAgent\"\n ],\n \"output\": \"user.hash\",\n \"length\": 16\n }\n }\n }\n }\n}"
216
+ }
217
+ ]
218
+ },
219
+ "fields-overview": {
220
+ "text": "Fields resolve from { event, ingest } via walkerOS mapping. Common patterns: ingest.ip (client IP), ingest.userAgent (browser UA), event.data.* (any event property). For time-based rotation use fn fields: daily rotation with toISOString().slice(0,10), monthly with getDate(). Order matters — same fields in different order produce different hashes. Use { key, fn } objects to transform before hashing (e.g., IP anonymization via the ipAnonymization step example)."
221
+ }
222
+ }
223
+ }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@walkeros/server-transformer-fingerprint",
3
3
  "description": "Fingerprint transformer for walkerOS server - hash configurable fields for session continuity",
4
- "version": "4.0.0",
4
+ "version": "4.0.1-next-1778183328892",
5
5
  "license": "MIT",
6
6
  "main": "./dist/index.js",
7
7
  "module": "./dist/index.mjs",
@@ -11,7 +11,8 @@
11
11
  "types": "./dist/index.d.ts",
12
12
  "import": "./dist/index.mjs",
13
13
  "require": "./dist/index.js"
14
- }
14
+ },
15
+ "./walkerOS.json": "./dist/walkerOS.json"
15
16
  },
16
17
  "files": [
17
18
  "dist/**"
@@ -20,17 +21,14 @@
20
21
  "build": "tsup --silent",
21
22
  "clean": "rm -rf .turbo && rm -rf node_modules && rm -rf dist",
22
23
  "dev": "jest --watchAll --colors",
23
- "lint": "tsc --noEmit && eslint \"**/*.ts*\"",
24
+ "typecheck": "tsc --noEmit",
25
+ "lint": "eslint \"**/*.ts*\"",
24
26
  "test": "jest",
25
27
  "update": "npx npm-check-updates -u && npm update"
26
28
  },
27
- "peerDependencies": {
28
- "@walkeros/core": "^1.0.0",
29
- "@walkeros/server-core": "^1.0.0"
30
- },
31
29
  "devDependencies": {
32
- "@walkeros/core": "1.0.0",
33
- "@walkeros/server-core": "1.0.0"
30
+ "@walkeros/core": "4.0.1-next-1778183328892",
31
+ "@walkeros/server-core": "4.0.1-next-1778183328892"
34
32
  },
35
33
  "repository": {
36
34
  "url": "git+https://github.com/elbwalker/walkerOS.git",
@@ -41,8 +39,14 @@
41
39
  "bugs": {
42
40
  "url": "https://github.com/elbwalker/walkerOS/issues"
43
41
  },
42
+ "walkerOS": {
43
+ "type": "transformer",
44
+ "platform": [
45
+ "server"
46
+ ],
47
+ "docs": "https://www.walkeros.io/docs/transformers/fingerprint"
48
+ },
44
49
  "keywords": [
45
- "walker",
46
50
  "walkerOS",
47
51
  "transformer",
48
52
  "fingerprint",
@@ -54,5 +58,9 @@
54
58
  "type": "GitHub Sponsors",
55
59
  "url": "https://github.com/sponsors/elbwalker"
56
60
  }
57
- ]
61
+ ],
62
+ "dependencies": {
63
+ "@walkeros/core": "4.0.1-next-1778183328892",
64
+ "@walkeros/server-core": "4.0.1-next-1778183328892"
65
+ }
58
66
  }