@kernhq/module-hr 0.1.0
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/LICENSE +662 -0
- package/README.md +71 -0
- package/dist/contract/capabilities.d.ts +45 -0
- package/dist/contract/capabilities.d.ts.map +1 -0
- package/dist/contract/capabilities.js +114 -0
- package/dist/contract/capabilities.js.map +1 -0
- package/dist/contract/events.d.ts +74 -0
- package/dist/contract/events.d.ts.map +1 -0
- package/dist/contract/events.js +63 -0
- package/dist/contract/events.js.map +1 -0
- package/dist/contract/index.d.ts +15 -0
- package/dist/contract/index.d.ts.map +1 -0
- package/dist/contract/index.js +15 -0
- package/dist/contract/index.js.map +1 -0
- package/dist/contract/models.d.ts +457 -0
- package/dist/contract/models.d.ts.map +1 -0
- package/dist/contract/models.js +381 -0
- package/dist/contract/models.js.map +1 -0
- package/dist/contract/permissions.d.ts +174 -0
- package/dist/contract/permissions.d.ts.map +1 -0
- package/dist/contract/permissions.js +209 -0
- package/dist/contract/permissions.js.map +1 -0
- package/dist/contract/router.d.ts +2720 -0
- package/dist/contract/router.d.ts.map +1 -0
- package/dist/contract/router.js +520 -0
- package/dist/contract/router.js.map +1 -0
- package/dist/contract/settings.d.ts +20 -0
- package/dist/contract/settings.d.ts.map +1 -0
- package/dist/contract/settings.js +34 -0
- package/dist/contract/settings.js.map +1 -0
- package/dist/policy/calendar.d.ts +64 -0
- package/dist/policy/calendar.d.ts.map +1 -0
- package/dist/policy/calendar.js +113 -0
- package/dist/policy/calendar.js.map +1 -0
- package/dist/server/index.d.ts +8 -0
- package/dist/server/index.d.ts.map +1 -0
- package/dist/server/index.js +141 -0
- package/dist/server/index.js.map +1 -0
- package/dist/server/packs/index.d.ts +50 -0
- package/dist/server/packs/index.d.ts.map +1 -0
- package/dist/server/packs/index.js +121 -0
- package/dist/server/packs/index.js.map +1 -0
- package/dist/server/router.d.ts +3095 -0
- package/dist/server/router.d.ts.map +1 -0
- package/dist/server/router.js +1537 -0
- package/dist/server/router.js.map +1 -0
- package/dist/server/schema.d.ts +2828 -0
- package/dist/server/schema.d.ts.map +1 -0
- package/dist/server/schema.js +313 -0
- package/dist/server/schema.js.map +1 -0
- package/dist/server/services/db.d.ts +22 -0
- package/dist/server/services/db.d.ts.map +1 -0
- package/dist/server/services/db.js +23 -0
- package/dist/server/services/db.js.map +1 -0
- package/dist/server/services/people.d.ts +110 -0
- package/dist/server/services/people.d.ts.map +1 -0
- package/dist/server/services/people.js +182 -0
- package/dist/server/services/people.js.map +1 -0
- package/dist/server/services/resolve.d.ts +66 -0
- package/dist/server/services/resolve.d.ts.map +1 -0
- package/dist/server/services/resolve.js +145 -0
- package/dist/server/services/resolve.js.map +1 -0
- package/migrations/0000_init.sql +223 -0
- package/migrations/0001_rls.sql +141 -0
- package/migrations/meta/0000_snapshot.json +1715 -0
- package/migrations/meta/_journal.json +20 -0
- package/package.json +59 -0
- package/src/client/index.ts +62 -0
- package/src/contract/capabilities.ts +117 -0
- package/src/contract/events.ts +84 -0
- package/src/contract/index.ts +14 -0
- package/src/contract/models.ts +437 -0
- package/src/contract/permissions.ts +217 -0
- package/src/contract/router.ts +611 -0
- package/src/contract/settings.ts +35 -0
package/README.md
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# Module template
|
|
2
|
+
|
|
3
|
+
Copy this to start a Kern module. It is a working module: a `Note` entity with list, create and
|
|
4
|
+
delete, its own Postgres schema, row-level security, permissions, events, and a test that refuses to
|
|
5
|
+
let the contract and the router drift apart.
|
|
6
|
+
|
|
7
|
+
The fastest way to copy it correctly is not to copy it by hand:
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
cd repos/modules
|
|
11
|
+
pnpm new-module crm # generates this package *and* its half in the app
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
Read the rest of this file if you are doing it by hand, or if you want to know what the generator
|
|
15
|
+
wrote.
|
|
16
|
+
|
|
17
|
+
## What is in here
|
|
18
|
+
|
|
19
|
+
| File | What it is |
|
|
20
|
+
|---|---|
|
|
21
|
+
| `src/contract.ts` | Zod models, the oRPC contract, events, permission keys, capabilities. Imported by both halves, so no Node code. |
|
|
22
|
+
| `src/server/schema.ts` | Drizzle tables in `mod_<id>`. |
|
|
23
|
+
| `src/server/_impl.ts` | The router. Separate from `index.ts` so the test can walk it without a kernel. |
|
|
24
|
+
| `src/server/index.ts` | `defineServerModule` — schema, migrations, router, subscriptions. |
|
|
25
|
+
| `src/client/index.ts` | The typed API client and module logic. **Ships as source.** |
|
|
26
|
+
| `src/module.test.ts` | Contract-to-router parity and the authorisation guard. Keep it. |
|
|
27
|
+
| `migrations/0000_init.sql` | Generated by `pnpm db:generate`. |
|
|
28
|
+
| `migrations/0001_rls.sql` | Hand-written. Never generated. |
|
|
29
|
+
|
|
30
|
+
## Copying it by hand
|
|
31
|
+
|
|
32
|
+
Each of these has been got wrong before.
|
|
33
|
+
|
|
34
|
+
1. **`package.json`** — set `name` to `@kernhq/module-<id>`, and **delete `"private": true`**. A
|
|
35
|
+
private package is skipped silently by changesets: the commit lands, CI is green, and nothing
|
|
36
|
+
publishes.
|
|
37
|
+
2. **`files`** must cover every directory `./client` reaches — `src/client` *and* `src/contract`.
|
|
38
|
+
The client ships as source, so a re-export the tarball omits breaks the consumer and nothing
|
|
39
|
+
local notices, because the workspace resolves the file the package does not ship.
|
|
40
|
+
`pnpm check:pack` catches it.
|
|
41
|
+
3. **The id agrees in four places**: `MODULE_ID`, `moduleSchema('<id>')`, `schemaFilter` in
|
|
42
|
+
`drizzle.config.ts`, and every permission and event prefix.
|
|
43
|
+
4. **Version comes from the package**, never a literal: `packageVersion(import.meta.url)`. A literal
|
|
44
|
+
is not bumped by a release — chat once shipped as 0.2.0 while telling every admin it was 0.1.0,
|
|
45
|
+
and that literal is what `workspace_modules.installed_version` recorded.
|
|
46
|
+
5. **Write the RLS migration.** `pnpm db:generate` will not. Copy `0001_rls.sql` and change the
|
|
47
|
+
table names; `rlsPolicySql` from `@kernhq/kernel` emits the same text.
|
|
48
|
+
6. **Host it.** A module nothing imports is invisible: its tests pass, it publishes, and every call
|
|
49
|
+
404s. Add it to `featureModules` in the `core` repo's `src/service.ts`, or to whichever service
|
|
50
|
+
should hold it.
|
|
51
|
+
7. **Register the client** in the app's `src/lib/modules/registry.ts`.
|
|
52
|
+
|
|
53
|
+
## What a module can contribute
|
|
54
|
+
|
|
55
|
+
The server half declares tables, migrations, a router, `procedures` other modules call through
|
|
56
|
+
`kernel.call()`, `jobs`, `subscriptions`, search indexers and lifecycle hooks.
|
|
57
|
+
|
|
58
|
+
The client half — the manifest in the app — declares `nav`, `commands`, `settingsPages`, `widgets`
|
|
59
|
+
for the dashboard, and `sidebar` for the column beside the rail. Read the `kern-widget` skill before
|
|
60
|
+
writing a widget, and `kern-module` for the whole sequence.
|
|
61
|
+
|
|
62
|
+
## Before you call it done
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
pnpm typecheck && pnpm lint && pnpm test && pnpm build
|
|
66
|
+
pnpm check:pack # the tarball contains what ./client imports
|
|
67
|
+
pnpm check:versions # the manifest version matches package.json
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Then use it through the interface, signed in, with the module enabled for a workspace. A module that
|
|
71
|
+
has never served a request is not finished, whatever the type-checker says.
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* How much HR this workspace has.
|
|
3
|
+
*
|
|
4
|
+
* HR is the module capabilities were built for. One company wants a staff directory and nothing
|
|
5
|
+
* else; a second wants leave, balances and approvals; a third runs shift rosters and clocks people
|
|
6
|
+
* in at a factory gate. Those are three products under one name, and the alternatives to this
|
|
7
|
+
* registry are a code fork per customer or a navigation rail full of features nobody uses.
|
|
8
|
+
*
|
|
9
|
+
* **A capability is declared here only once something is behind it.** A switch that changes nothing
|
|
10
|
+
* is worse than a missing switch: it teaches an administrator that the switchboard does not mean
|
|
11
|
+
* anything. So this list grows with the module rather than describing where the module is going —
|
|
12
|
+
* `leave`, `attendance`, `overtime`, `rosters`, `periods` and `payroll_export` arrive with the
|
|
13
|
+
* phases that implement them.
|
|
14
|
+
*
|
|
15
|
+
* Two rules that decide whether something belongs here at all:
|
|
16
|
+
*
|
|
17
|
+
* - **Not a permission.** "May Ayşe approve leave" is a permission — true for her, false for someone
|
|
18
|
+
* else, in the same workspace. "Does this company do leave" is a capability: one answer for
|
|
19
|
+
* everyone, the owner included.
|
|
20
|
+
* - **Reversible without a migration.** Switching one off writes a boolean into module settings;
|
|
21
|
+
* the rows stay exactly where they are and switching it back on restores them. Anything that would
|
|
22
|
+
* need data thrown away is not a capability, however much it looks like one.
|
|
23
|
+
*/
|
|
24
|
+
export declare const hrCapabilities: readonly {
|
|
25
|
+
id: string;
|
|
26
|
+
label: string;
|
|
27
|
+
dependsOn: string[];
|
|
28
|
+
defaultEnabled: boolean;
|
|
29
|
+
level: 3 | 1 | 2;
|
|
30
|
+
required: boolean;
|
|
31
|
+
description?: string | undefined;
|
|
32
|
+
}[];
|
|
33
|
+
export type HrCapabilityId = (typeof hrCapabilities)[number]['id'];
|
|
34
|
+
/**
|
|
35
|
+
* Which procedures sit behind which capability.
|
|
36
|
+
*
|
|
37
|
+
* Declared as data because a missing `requiresCapability` is invisible: the procedure compiles,
|
|
38
|
+
* every other test passes, and the only symptom is a workspace calling a feature it switched off.
|
|
39
|
+
* `module.test.ts` reads this and fails when a procedure named here is not carrying the middleware.
|
|
40
|
+
*
|
|
41
|
+
* A procedure absent from this map belongs to the module as a whole and is reachable whenever HR is
|
|
42
|
+
* on — which for `core` is always, because it is `required`.
|
|
43
|
+
*/
|
|
44
|
+
export declare const hrCapabilityProcedures: Record<string, readonly string[]>;
|
|
45
|
+
//# sourceMappingURL=capabilities.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"capabilities.d.ts","sourceRoot":"","sources":["../../src/contract/capabilities.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,eAAO,MAAM,cAAc;;;;;;;;GAiDzB,CAAA;AAEF,MAAM,MAAM,cAAc,GAAG,CAAC,OAAO,cAAc,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,CAAA;AAElE;;;;;;;;;GASG;AACH,eAAO,MAAM,sBAAsB,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CA4BpE,CAAA"}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { defineCapabilities } from '@kernhq/contracts';
|
|
2
|
+
/**
|
|
3
|
+
* How much HR this workspace has.
|
|
4
|
+
*
|
|
5
|
+
* HR is the module capabilities were built for. One company wants a staff directory and nothing
|
|
6
|
+
* else; a second wants leave, balances and approvals; a third runs shift rosters and clocks people
|
|
7
|
+
* in at a factory gate. Those are three products under one name, and the alternatives to this
|
|
8
|
+
* registry are a code fork per customer or a navigation rail full of features nobody uses.
|
|
9
|
+
*
|
|
10
|
+
* **A capability is declared here only once something is behind it.** A switch that changes nothing
|
|
11
|
+
* is worse than a missing switch: it teaches an administrator that the switchboard does not mean
|
|
12
|
+
* anything. So this list grows with the module rather than describing where the module is going —
|
|
13
|
+
* `leave`, `attendance`, `overtime`, `rosters`, `periods` and `payroll_export` arrive with the
|
|
14
|
+
* phases that implement them.
|
|
15
|
+
*
|
|
16
|
+
* Two rules that decide whether something belongs here at all:
|
|
17
|
+
*
|
|
18
|
+
* - **Not a permission.** "May Ayşe approve leave" is a permission — true for her, false for someone
|
|
19
|
+
* else, in the same workspace. "Does this company do leave" is a capability: one answer for
|
|
20
|
+
* everyone, the owner included.
|
|
21
|
+
* - **Reversible without a migration.** Switching one off writes a boolean into module settings;
|
|
22
|
+
* the rows stay exactly where they are and switching it back on restores them. Anything that would
|
|
23
|
+
* need data thrown away is not a capability, however much it looks like one.
|
|
24
|
+
*/
|
|
25
|
+
export const hrCapabilities = defineCapabilities([
|
|
26
|
+
{
|
|
27
|
+
id: 'core',
|
|
28
|
+
label: 'People',
|
|
29
|
+
description: 'The staff directory, employment records and reporting lines',
|
|
30
|
+
required: true,
|
|
31
|
+
level: 1,
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
id: 'offices',
|
|
35
|
+
label: 'Offices',
|
|
36
|
+
description: 'More than one place of work, each with its own country, timezone and holidays',
|
|
37
|
+
dependsOn: ['core'],
|
|
38
|
+
// Off by default, and invisible when off — but the *concept* is never absent. A workspace always
|
|
39
|
+
// has exactly one office, built from its country when HR is enabled, and everybody is assigned
|
|
40
|
+
// to it. Switching this on reveals the list and the assignment control; it does not migrate
|
|
41
|
+
// anything, because the shape was there from the first day. A workspace that only ever has one
|
|
42
|
+
// office never meets the word.
|
|
43
|
+
defaultEnabled: false,
|
|
44
|
+
level: 2,
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
id: 'legal_entities',
|
|
48
|
+
label: 'Legal entities',
|
|
49
|
+
description: 'Several employing companies, for a group operating across borders',
|
|
50
|
+
// Depends on offices rather than core: a single-site company has one employer by definition, and
|
|
51
|
+
// the question only becomes real once there is more than one place of work.
|
|
52
|
+
dependsOn: ['offices'],
|
|
53
|
+
defaultEnabled: false,
|
|
54
|
+
level: 3,
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
id: 'calendars',
|
|
58
|
+
label: 'Holiday calendars',
|
|
59
|
+
description: 'Public holidays, company closures and the working week',
|
|
60
|
+
dependsOn: ['core'],
|
|
61
|
+
// On by default. Every company has holidays, and a directory that does not know when people are
|
|
62
|
+
// off is answering a question nobody asked.
|
|
63
|
+
defaultEnabled: true,
|
|
64
|
+
level: 1,
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
id: 'documents',
|
|
68
|
+
label: 'Employee documents',
|
|
69
|
+
description: 'Contracts, identity documents and certificates against a person',
|
|
70
|
+
dependsOn: ['core'],
|
|
71
|
+
defaultEnabled: false,
|
|
72
|
+
level: 2,
|
|
73
|
+
},
|
|
74
|
+
]);
|
|
75
|
+
/**
|
|
76
|
+
* Which procedures sit behind which capability.
|
|
77
|
+
*
|
|
78
|
+
* Declared as data because a missing `requiresCapability` is invisible: the procedure compiles,
|
|
79
|
+
* every other test passes, and the only symptom is a workspace calling a feature it switched off.
|
|
80
|
+
* `module.test.ts` reads this and fails when a procedure named here is not carrying the middleware.
|
|
81
|
+
*
|
|
82
|
+
* A procedure absent from this map belongs to the module as a whole and is reachable whenever HR is
|
|
83
|
+
* on — which for `core` is always, because it is `required`.
|
|
84
|
+
*/
|
|
85
|
+
export const hrCapabilityProcedures = {
|
|
86
|
+
offices: [
|
|
87
|
+
'offices.list',
|
|
88
|
+
'offices.get',
|
|
89
|
+
'offices.create',
|
|
90
|
+
'offices.update',
|
|
91
|
+
'offices.archive',
|
|
92
|
+
'offices.setDefault',
|
|
93
|
+
'offices.assign',
|
|
94
|
+
'offices.unassign',
|
|
95
|
+
'offices.people',
|
|
96
|
+
],
|
|
97
|
+
legal_entities: ['entities.list', 'entities.get', 'entities.create', 'entities.update', 'entities.archive'],
|
|
98
|
+
calendars: [
|
|
99
|
+
'calendars.list',
|
|
100
|
+
'calendars.get',
|
|
101
|
+
'calendars.create',
|
|
102
|
+
'calendars.update',
|
|
103
|
+
'calendars.archive',
|
|
104
|
+
'calendars.days.list',
|
|
105
|
+
'calendars.days.add',
|
|
106
|
+
'calendars.days.update',
|
|
107
|
+
'calendars.days.remove',
|
|
108
|
+
'calendars.pack.preview',
|
|
109
|
+
'calendars.pack.apply',
|
|
110
|
+
'calendars.workingDays',
|
|
111
|
+
],
|
|
112
|
+
documents: ['documents.list', 'documents.attach', 'documents.remove'],
|
|
113
|
+
};
|
|
114
|
+
//# sourceMappingURL=capabilities.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"capabilities.js","sourceRoot":"","sources":["../../src/contract/capabilities.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAA;AAEtD;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG,kBAAkB,CAAC;IAC/C;QACE,EAAE,EAAE,MAAM;QACV,KAAK,EAAE,QAAQ;QACf,WAAW,EAAE,6DAA6D;QAC1E,QAAQ,EAAE,IAAI;QACd,KAAK,EAAE,CAAC;KACT;IACD;QACE,EAAE,EAAE,SAAS;QACb,KAAK,EAAE,SAAS;QAChB,WAAW,EAAE,+EAA+E;QAC5F,SAAS,EAAE,CAAC,MAAM,CAAC;QACnB,iGAAiG;QACjG,+FAA+F;QAC/F,4FAA4F;QAC5F,+FAA+F;QAC/F,+BAA+B;QAC/B,cAAc,EAAE,KAAK;QACrB,KAAK,EAAE,CAAC;KACT;IACD;QACE,EAAE,EAAE,gBAAgB;QACpB,KAAK,EAAE,gBAAgB;QACvB,WAAW,EAAE,mEAAmE;QAChF,iGAAiG;QACjG,4EAA4E;QAC5E,SAAS,EAAE,CAAC,SAAS,CAAC;QACtB,cAAc,EAAE,KAAK;QACrB,KAAK,EAAE,CAAC;KACT;IACD;QACE,EAAE,EAAE,WAAW;QACf,KAAK,EAAE,mBAAmB;QAC1B,WAAW,EAAE,wDAAwD;QACrE,SAAS,EAAE,CAAC,MAAM,CAAC;QACnB,gGAAgG;QAChG,4CAA4C;QAC5C,cAAc,EAAE,IAAI;QACpB,KAAK,EAAE,CAAC;KACT;IACD;QACE,EAAE,EAAE,WAAW;QACf,KAAK,EAAE,oBAAoB;QAC3B,WAAW,EAAE,iEAAiE;QAC9E,SAAS,EAAE,CAAC,MAAM,CAAC;QACnB,cAAc,EAAE,KAAK;QACrB,KAAK,EAAE,CAAC;KACT;CACF,CAAC,CAAA;AAIF;;;;;;;;;GASG;AACH,MAAM,CAAC,MAAM,sBAAsB,GAAsC;IACvE,OAAO,EAAE;QACP,cAAc;QACd,aAAa;QACb,gBAAgB;QAChB,gBAAgB;QAChB,iBAAiB;QACjB,oBAAoB;QACpB,gBAAgB;QAChB,kBAAkB;QAClB,gBAAgB;KACjB;IACD,cAAc,EAAE,CAAC,eAAe,EAAE,cAAc,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,kBAAkB,CAAC;IAC3G,SAAS,EAAE;QACT,gBAAgB;QAChB,eAAe;QACf,kBAAkB;QAClB,kBAAkB;QAClB,mBAAmB;QACnB,qBAAqB;QACrB,oBAAoB;QACpB,uBAAuB;QACvB,uBAAuB;QACvB,wBAAwB;QACxB,sBAAsB;QACtB,uBAAuB;KACxB;IACD,SAAS,EAAE,CAAC,gBAAgB,EAAE,kBAAkB,EAAE,kBAAkB,CAAC;CACtE,CAAA"}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
/**
|
|
3
|
+
* `hr.<entity>.<action>`. Anything that emits one declares it here.
|
|
4
|
+
*
|
|
5
|
+
* These are what the rest of the product reacts to. Chat wants to know when somebody joins so it can
|
|
6
|
+
* add them to a channel; a future calendar wants office holidays; payroll wants an employment
|
|
7
|
+
* change. The payloads carry ids rather than rows on purpose — a subscriber that needs the record
|
|
8
|
+
* asks for it with its own principal, so an event cannot become a way to read data past a permission
|
|
9
|
+
* check.
|
|
10
|
+
*/
|
|
11
|
+
export declare const hrEvents: {
|
|
12
|
+
personCreated: import("@kernhq/contracts").EventDef<"hr.person.created", z.ZodObject<{
|
|
13
|
+
personId: z.ZodUUID;
|
|
14
|
+
workspaceId: z.core.$ZodBranded<z.ZodUUID, "WorkspaceId", "out">;
|
|
15
|
+
userId: z.ZodNullable<z.ZodUUID>;
|
|
16
|
+
}, z.core.$strip>>;
|
|
17
|
+
personUpdated: import("@kernhq/contracts").EventDef<"hr.person.updated", z.ZodObject<{
|
|
18
|
+
personId: z.ZodUUID;
|
|
19
|
+
workspaceId: z.core.$ZodBranded<z.ZodUUID, "WorkspaceId", "out">;
|
|
20
|
+
fields: z.ZodArray<z.ZodString>;
|
|
21
|
+
}, z.core.$strip>>;
|
|
22
|
+
/**
|
|
23
|
+
* Status moved — onboarding to active, active to terminated.
|
|
24
|
+
*
|
|
25
|
+
* Separate from `personUpdated` because the things that care about a lifecycle change (revoking
|
|
26
|
+
* access, closing a leave balance, ending a payroll line) do not want to filter every profile
|
|
27
|
+
* edit to find it.
|
|
28
|
+
*/
|
|
29
|
+
personStatusChanged: import("@kernhq/contracts").EventDef<"hr.person.status_changed", z.ZodObject<{
|
|
30
|
+
personId: z.ZodUUID;
|
|
31
|
+
workspaceId: z.core.$ZodBranded<z.ZodUUID, "WorkspaceId", "out">;
|
|
32
|
+
from: z.ZodString;
|
|
33
|
+
to: z.ZodString;
|
|
34
|
+
on: z.ZodISODate;
|
|
35
|
+
}, z.core.$strip>>;
|
|
36
|
+
employmentChanged: import("@kernhq/contracts").EventDef<"hr.employment.changed", z.ZodObject<{
|
|
37
|
+
personId: z.ZodUUID;
|
|
38
|
+
workspaceId: z.core.$ZodBranded<z.ZodUUID, "WorkspaceId", "out">;
|
|
39
|
+
employmentId: z.ZodUUID;
|
|
40
|
+
effectiveFrom: z.ZodISODate;
|
|
41
|
+
}, z.core.$strip>>;
|
|
42
|
+
/**
|
|
43
|
+
* Somebody's office changed, or their primary did.
|
|
44
|
+
*
|
|
45
|
+
* Worth its own event because the primary office decides holidays, timezone and policy: anything
|
|
46
|
+
* holding a derived answer for this person has to recompute, and this is how it finds out.
|
|
47
|
+
*/
|
|
48
|
+
officeAssignmentChanged: import("@kernhq/contracts").EventDef<"hr.office_assignment.changed", z.ZodObject<{
|
|
49
|
+
personId: z.ZodUUID;
|
|
50
|
+
workspaceId: z.core.$ZodBranded<z.ZodUUID, "WorkspaceId", "out">;
|
|
51
|
+
officeId: z.ZodUUID;
|
|
52
|
+
isPrimary: z.ZodBoolean;
|
|
53
|
+
effectiveFrom: z.ZodISODate;
|
|
54
|
+
}, z.core.$strip>>;
|
|
55
|
+
officeCreated: import("@kernhq/contracts").EventDef<"hr.office.created", z.ZodObject<{
|
|
56
|
+
officeId: z.ZodUUID;
|
|
57
|
+
workspaceId: z.core.$ZodBranded<z.ZodUUID, "WorkspaceId", "out">;
|
|
58
|
+
country: z.ZodString;
|
|
59
|
+
}, z.core.$strip>>;
|
|
60
|
+
/**
|
|
61
|
+
* A calendar's days changed — a holiday added, a pack applied.
|
|
62
|
+
*
|
|
63
|
+
* Everything derived from a calendar (working days, leave day counts, later the attendance day
|
|
64
|
+
* sheet) is stale from here. The payload names the date range touched so a consumer can recompute
|
|
65
|
+
* that window rather than everything.
|
|
66
|
+
*/
|
|
67
|
+
calendarChanged: import("@kernhq/contracts").EventDef<"hr.calendar.changed", z.ZodObject<{
|
|
68
|
+
calendarId: z.ZodUUID;
|
|
69
|
+
workspaceId: z.core.$ZodBranded<z.ZodUUID, "WorkspaceId", "out">;
|
|
70
|
+
from: z.ZodNullable<z.ZodISODate>;
|
|
71
|
+
to: z.ZodNullable<z.ZodISODate>;
|
|
72
|
+
}, z.core.$strip>>;
|
|
73
|
+
};
|
|
74
|
+
//# sourceMappingURL=events.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"events.d.ts","sourceRoot":"","sources":["../../src/contract/events.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAEvB;;;;;;;;GAQG;AACH,eAAO,MAAM,QAAQ;;;;;;;;;;;IASnB;;;;;;OAMG;;;;;;;;;;;;;;IAoBH;;;;;OAKG;;;;;;;;;;;;;IAeH;;;;;;OAMG;;;;;;;CAUJ,CAAA"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { defineEvent, WorkspaceId } from '@kernhq/contracts';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
/**
|
|
4
|
+
* `hr.<entity>.<action>`. Anything that emits one declares it here.
|
|
5
|
+
*
|
|
6
|
+
* These are what the rest of the product reacts to. Chat wants to know when somebody joins so it can
|
|
7
|
+
* add them to a channel; a future calendar wants office holidays; payroll wants an employment
|
|
8
|
+
* change. The payloads carry ids rather than rows on purpose — a subscriber that needs the record
|
|
9
|
+
* asks for it with its own principal, so an event cannot become a way to read data past a permission
|
|
10
|
+
* check.
|
|
11
|
+
*/
|
|
12
|
+
export const hrEvents = {
|
|
13
|
+
personCreated: defineEvent('hr.person.created', z.object({ personId: z.uuid(), workspaceId: WorkspaceId, userId: z.uuid().nullable() })),
|
|
14
|
+
personUpdated: defineEvent('hr.person.updated', z.object({ personId: z.uuid(), workspaceId: WorkspaceId, fields: z.array(z.string()) })),
|
|
15
|
+
/**
|
|
16
|
+
* Status moved — onboarding to active, active to terminated.
|
|
17
|
+
*
|
|
18
|
+
* Separate from `personUpdated` because the things that care about a lifecycle change (revoking
|
|
19
|
+
* access, closing a leave balance, ending a payroll line) do not want to filter every profile
|
|
20
|
+
* edit to find it.
|
|
21
|
+
*/
|
|
22
|
+
personStatusChanged: defineEvent('hr.person.status_changed', z.object({
|
|
23
|
+
personId: z.uuid(),
|
|
24
|
+
workspaceId: WorkspaceId,
|
|
25
|
+
from: z.string(),
|
|
26
|
+
to: z.string(),
|
|
27
|
+
on: z.iso.date(),
|
|
28
|
+
})),
|
|
29
|
+
employmentChanged: defineEvent('hr.employment.changed', z.object({
|
|
30
|
+
personId: z.uuid(),
|
|
31
|
+
workspaceId: WorkspaceId,
|
|
32
|
+
employmentId: z.uuid(),
|
|
33
|
+
effectiveFrom: z.iso.date(),
|
|
34
|
+
})),
|
|
35
|
+
/**
|
|
36
|
+
* Somebody's office changed, or their primary did.
|
|
37
|
+
*
|
|
38
|
+
* Worth its own event because the primary office decides holidays, timezone and policy: anything
|
|
39
|
+
* holding a derived answer for this person has to recompute, and this is how it finds out.
|
|
40
|
+
*/
|
|
41
|
+
officeAssignmentChanged: defineEvent('hr.office_assignment.changed', z.object({
|
|
42
|
+
personId: z.uuid(),
|
|
43
|
+
workspaceId: WorkspaceId,
|
|
44
|
+
officeId: z.uuid(),
|
|
45
|
+
isPrimary: z.boolean(),
|
|
46
|
+
effectiveFrom: z.iso.date(),
|
|
47
|
+
})),
|
|
48
|
+
officeCreated: defineEvent('hr.office.created', z.object({ officeId: z.uuid(), workspaceId: WorkspaceId, country: z.string() })),
|
|
49
|
+
/**
|
|
50
|
+
* A calendar's days changed — a holiday added, a pack applied.
|
|
51
|
+
*
|
|
52
|
+
* Everything derived from a calendar (working days, leave day counts, later the attendance day
|
|
53
|
+
* sheet) is stale from here. The payload names the date range touched so a consumer can recompute
|
|
54
|
+
* that window rather than everything.
|
|
55
|
+
*/
|
|
56
|
+
calendarChanged: defineEvent('hr.calendar.changed', z.object({
|
|
57
|
+
calendarId: z.uuid(),
|
|
58
|
+
workspaceId: WorkspaceId,
|
|
59
|
+
from: z.iso.date().nullable(),
|
|
60
|
+
to: z.iso.date().nullable(),
|
|
61
|
+
})),
|
|
62
|
+
};
|
|
63
|
+
//# sourceMappingURL=events.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"events.js","sourceRoot":"","sources":["../../src/contract/events.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AAC5D,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAEvB;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,QAAQ,GAAG;IACtB,aAAa,EAAE,WAAW,CACxB,mBAAmB,EACnB,CAAC,CAAC,MAAM,CAAC,EAAE,QAAQ,EAAE,CAAC,CAAC,IAAI,EAAE,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,QAAQ,EAAE,EAAE,CAAC,CACxF;IACD,aAAa,EAAE,WAAW,CACxB,mBAAmB,EACnB,CAAC,CAAC,MAAM,CAAC,EAAE,QAAQ,EAAE,CAAC,CAAC,IAAI,EAAE,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,EAAE,CAAC,CACxF;IACD;;;;;;OAMG;IACH,mBAAmB,EAAE,WAAW,CAC9B,0BAA0B,EAC1B,CAAC,CAAC,MAAM,CAAC;QACP,QAAQ,EAAE,CAAC,CAAC,IAAI,EAAE;QAClB,WAAW,EAAE,WAAW;QACxB,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE;QAChB,EAAE,EAAE,CAAC,CAAC,MAAM,EAAE;QACd,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,IAAI,EAAE;KACjB,CAAC,CACH;IACD,iBAAiB,EAAE,WAAW,CAC5B,uBAAuB,EACvB,CAAC,CAAC,MAAM,CAAC;QACP,QAAQ,EAAE,CAAC,CAAC,IAAI,EAAE;QAClB,WAAW,EAAE,WAAW;QACxB,YAAY,EAAE,CAAC,CAAC,IAAI,EAAE;QACtB,aAAa,EAAE,CAAC,CAAC,GAAG,CAAC,IAAI,EAAE;KAC5B,CAAC,CACH;IACD;;;;;OAKG;IACH,uBAAuB,EAAE,WAAW,CAClC,8BAA8B,EAC9B,CAAC,CAAC,MAAM,CAAC;QACP,QAAQ,EAAE,CAAC,CAAC,IAAI,EAAE;QAClB,WAAW,EAAE,WAAW;QACxB,QAAQ,EAAE,CAAC,CAAC,IAAI,EAAE;QAClB,SAAS,EAAE,CAAC,CAAC,OAAO,EAAE;QACtB,aAAa,EAAE,CAAC,CAAC,GAAG,CAAC,IAAI,EAAE;KAC5B,CAAC,CACH;IACD,aAAa,EAAE,WAAW,CACxB,mBAAmB,EACnB,CAAC,CAAC,MAAM,CAAC,EAAE,QAAQ,EAAE,CAAC,CAAC,IAAI,EAAE,EAAE,WAAW,EAAE,WAAW,EAAE,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAChF;IACD;;;;;;OAMG;IACH,eAAe,EAAE,WAAW,CAC1B,qBAAqB,EACrB,CAAC,CAAC,MAAM,CAAC;QACP,UAAU,EAAE,CAAC,CAAC,IAAI,EAAE;QACpB,WAAW,EAAE,WAAW;QACxB,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,QAAQ,EAAE;QAC7B,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,QAAQ,EAAE;KAC5B,CAAC,CACH;CACF,CAAA"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What HR offers, as data.
|
|
3
|
+
*
|
|
4
|
+
* Imported by **both** halves — the server implements it, the client calls it — so nothing here may
|
|
5
|
+
* touch Node. The contract is the only thing that crosses that line, which is why a procedure that
|
|
6
|
+
* exists here and not in the router is a lie that compiles. `module.test.ts` checks exactly that,
|
|
7
|
+
* and also that every procedure listed in `hrCapabilityProcedures` carries its capability guard.
|
|
8
|
+
*/
|
|
9
|
+
export * from './capabilities.js';
|
|
10
|
+
export * from './events.js';
|
|
11
|
+
export * from './models.js';
|
|
12
|
+
export * from './permissions.js';
|
|
13
|
+
export * from './router.js';
|
|
14
|
+
export * from './settings.js';
|
|
15
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/contract/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,cAAc,mBAAmB,CAAA;AACjC,cAAc,aAAa,CAAA;AAC3B,cAAc,aAAa,CAAA;AAC3B,cAAc,kBAAkB,CAAA;AAChC,cAAc,aAAa,CAAA;AAC3B,cAAc,eAAe,CAAA"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What HR offers, as data.
|
|
3
|
+
*
|
|
4
|
+
* Imported by **both** halves — the server implements it, the client calls it — so nothing here may
|
|
5
|
+
* touch Node. The contract is the only thing that crosses that line, which is why a procedure that
|
|
6
|
+
* exists here and not in the router is a lie that compiles. `module.test.ts` checks exactly that,
|
|
7
|
+
* and also that every procedure listed in `hrCapabilityProcedures` carries its capability guard.
|
|
8
|
+
*/
|
|
9
|
+
export * from './capabilities.js';
|
|
10
|
+
export * from './events.js';
|
|
11
|
+
export * from './models.js';
|
|
12
|
+
export * from './permissions.js';
|
|
13
|
+
export * from './router.js';
|
|
14
|
+
export * from './settings.js';
|
|
15
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/contract/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,cAAc,mBAAmB,CAAA;AACjC,cAAc,aAAa,CAAA;AAC3B,cAAc,aAAa,CAAA;AAC3B,cAAc,kBAAkB,CAAA;AAChC,cAAc,aAAa,CAAA;AAC3B,cAAc,eAAe,CAAA"}
|