@pepys/kit 0.0.0-stage → 0.0.1
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/README.md +63 -2
- package/package.json +34 -4
- package/src/constants.ts +1 -0
- package/src/consumers/define-consumer.ts +20 -0
- package/src/consumers/types.ts +48 -0
- package/src/delivery-checks/build-delivery-proof.ts +12 -0
- package/src/delivery-checks/is-delivery-authentic.ts +49 -0
- package/src/deployments/check-deployment.ts +444 -0
- package/src/deployments/define-deployment.ts +7 -0
- package/src/deployments/define-plugin.ts +56 -0
- package/src/deployments/list-receive-routes.ts +27 -0
- package/src/deployments/types.ts +110 -0
- package/src/entity-ids/build-entity-id.ts +16 -0
- package/src/entity-ids/entity-id-failure.ts +6 -0
- package/src/entity-ids/entity-id-parts.ts +5 -0
- package/src/entity-ids/fact-kinds.ts +3 -0
- package/src/entity-ids/find-entity-id-failure.ts +25 -0
- package/src/entity-ids/parse-entity-id.ts +27 -0
- package/src/fact-envelope-cases.json +464 -0
- package/src/facts/build-enrichment-fact-id.ts +15 -0
- package/src/facts/build-event-fact-id.ts +11 -0
- package/src/facts/build-fact-hash.ts +6 -0
- package/src/facts/build-manual-fact-id.ts +7 -0
- package/src/facts/build-observation-fact-id.ts +15 -0
- package/src/facts/build-webhook-fact-id.ts +11 -0
- package/src/facts/parse-fact-envelope.ts +44 -0
- package/src/facts/parse-stream-name.ts +33 -0
- package/src/generators/build-payload-shape.ts +75 -0
- package/src/generators/collect-view-kinds.ts +8 -0
- package/src/generators/format-column-type.ts +16 -0
- package/src/generators/format-entity-ids.ts +46 -0
- package/src/generators/format-fact-reader.ts +194 -0
- package/src/generators/format-literal.ts +13 -0
- package/src/generators/format-model-contract.ts +23 -0
- package/src/generators/format-operations.ts +109 -0
- package/src/generators/format-payload-shape.ts +181 -0
- package/src/generators/format-property-access.ts +6 -0
- package/src/generators/format-property-name.ts +6 -0
- package/src/generators/format-row-readers.ts +116 -0
- package/src/generators/generate-boundary-glue.ts +60 -0
- package/src/generators/generate-sqlite-schema.ts +78 -0
- package/src/generators/is-identifier.ts +5 -0
- package/src/generators/quote-sql-identifier.ts +3 -0
- package/src/generators/to-entity-id-type-name.ts +6 -0
- package/src/generators/to-pascal-case.ts +7 -0
- package/src/generators.ts +4 -0
- package/src/ids/types.ts +25 -0
- package/src/index.ts +36 -0
- package/src/link-types/define-link-type.ts +9 -0
- package/src/link-types/types.ts +15 -0
- package/src/links/find-link-problem.ts +53 -0
- package/src/links/link-check-cases.json +274 -0
- package/src/links/types.ts +22 -0
- package/src/queries/define-query.ts +14 -0
- package/src/queries/types.ts +56 -0
- package/src/send/create-fact-batch-sender.ts +143 -0
- package/src/send/types.ts +25 -0
- package/src/send.ts +2 -0
- package/src/sources/define-fact-family.ts +10 -0
- package/src/sources/define-source.ts +7 -0
- package/src/sources/read-fact-batch.ts +114 -0
- package/src/sources/receive-fact-batch.ts +11 -0
- package/src/sources/types.ts +154 -0
- package/src/store-reader/store-reader.ts +76 -0
- package/src/tables/define-table.ts +13 -0
- package/src/tables/table-row.ts +20 -0
- package/src/types.ts +47 -0
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { kitVersion } from '../constants.ts';
|
|
2
|
+
import type { ConsumerDeclaration } from '../consumers/types.ts';
|
|
3
|
+
import type { EntityKind, LinkTypeDeclaration } from '../link-types/types.ts';
|
|
4
|
+
import type { AnyQueryDefinition } from '../queries/types.ts';
|
|
5
|
+
import type { SourceDefinition } from '../sources/types.ts';
|
|
6
|
+
import type { Plugin, PluginSettings } from './types.ts';
|
|
7
|
+
|
|
8
|
+
interface PluginDeclaration<Query extends AnyQueryDefinition> {
|
|
9
|
+
readonly name: string;
|
|
10
|
+
readonly sources?: readonly SourceDefinition[] | undefined;
|
|
11
|
+
readonly consumers?: readonly ConsumerDeclaration[] | undefined;
|
|
12
|
+
readonly queries?: readonly Query[] | undefined;
|
|
13
|
+
readonly settings?: PluginSettings | undefined;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function definePlugin<const Query extends AnyQueryDefinition = never>(
|
|
17
|
+
plugin: PluginDeclaration<Query>,
|
|
18
|
+
): Plugin<Query> {
|
|
19
|
+
const sources = plugin.sources ?? [];
|
|
20
|
+
const consumers = plugin.consumers ?? [];
|
|
21
|
+
|
|
22
|
+
return {
|
|
23
|
+
kit: kitVersion,
|
|
24
|
+
name: plugin.name,
|
|
25
|
+
settings: plugin.settings,
|
|
26
|
+
sources,
|
|
27
|
+
consumers,
|
|
28
|
+
queries: plugin.queries ?? [],
|
|
29
|
+
linkTypes: collectLinkTypes(consumers),
|
|
30
|
+
entityKinds: collectEntityKinds(sources, consumers),
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function collectLinkTypes(consumers: readonly ConsumerDeclaration[]): LinkTypeDeclaration[] {
|
|
35
|
+
const byName = new Map<string, LinkTypeDeclaration>();
|
|
36
|
+
|
|
37
|
+
for (const linkType of consumers.flatMap((consumer) => consumer.links)) {
|
|
38
|
+
if (!byName.has(linkType.name)) {
|
|
39
|
+
byName.set(linkType.name, linkType);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return [...byName.values()];
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function collectEntityKinds(
|
|
47
|
+
sources: readonly SourceDefinition[],
|
|
48
|
+
consumers: readonly ConsumerDeclaration[],
|
|
49
|
+
): EntityKind[] {
|
|
50
|
+
const kinds = [
|
|
51
|
+
...sources.flatMap((source) => source.families.map((family) => family.entityKind)),
|
|
52
|
+
...consumers.flatMap((consumer) => consumer.entityViews.map((view) => view.kind)),
|
|
53
|
+
];
|
|
54
|
+
|
|
55
|
+
return [...new Set(kinds)];
|
|
56
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { Deployment, ReceiveRoute } from './types.ts';
|
|
2
|
+
|
|
3
|
+
export function listReceiveRoutes(deployment: Deployment): ReceiveRoute[] {
|
|
4
|
+
const routes: ReceiveRoute[] = [];
|
|
5
|
+
|
|
6
|
+
for (const source of deployment.plugins.flatMap((plugin) => plugin.sources)) {
|
|
7
|
+
const path = source.live;
|
|
8
|
+
|
|
9
|
+
if (path?.kind !== 'receive') {
|
|
10
|
+
continue;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const reach = path.reach ?? 'private';
|
|
14
|
+
|
|
15
|
+
for (const route of [path.route, ...(path.ignoredRoutes ?? [])]) {
|
|
16
|
+
routes.push({
|
|
17
|
+
source: source.name,
|
|
18
|
+
route,
|
|
19
|
+
check: path.check.kind,
|
|
20
|
+
reach,
|
|
21
|
+
stores: route === path.route,
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return routes;
|
|
27
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import type { z } from 'zod';
|
|
2
|
+
import type { ConsumerDeclaration } from '../consumers/types.ts';
|
|
3
|
+
import type { EntityKind, LinkTypeDeclaration } from '../link-types/types.ts';
|
|
4
|
+
import type { AnyQueryDefinition } from '../queries/types.ts';
|
|
5
|
+
import type { DeliveryCheck, SourceDefinition } from '../sources/types.ts';
|
|
6
|
+
import type { DeclaredStreamName } from '../types.ts';
|
|
7
|
+
|
|
8
|
+
export type SettingValue = string | number | boolean;
|
|
9
|
+
|
|
10
|
+
export interface Plugin<Query extends AnyQueryDefinition = AnyQueryDefinition> {
|
|
11
|
+
// the kit version the plug-in was declared with
|
|
12
|
+
readonly kit: number;
|
|
13
|
+
readonly name: string;
|
|
14
|
+
readonly settings: PluginSettings | undefined;
|
|
15
|
+
readonly sources: readonly SourceDefinition[];
|
|
16
|
+
readonly consumers: readonly ConsumerDeclaration[];
|
|
17
|
+
readonly queries: readonly Query[];
|
|
18
|
+
|
|
19
|
+
// every consumer's `links`, once per name
|
|
20
|
+
readonly linkTypes: readonly LinkTypeDeclaration[];
|
|
21
|
+
|
|
22
|
+
// every family's `entityKind` and every entity view's `kind`, once each
|
|
23
|
+
readonly entityKinds: readonly EntityKind[];
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Compile-time settings: `pepys build` writes the checked values into the glue of each consumer of
|
|
27
|
+
// the plug-in, so a consumer reads no setting at run time and a new value is a new version.
|
|
28
|
+
export interface PluginSettings<Schema extends z.ZodObject = z.ZodObject> {
|
|
29
|
+
// a flat object of strings, numbers and booleans
|
|
30
|
+
readonly schema: Schema;
|
|
31
|
+
readonly defaults: z.output<Schema>;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface Deployment<Plugins extends readonly Plugin[] = readonly Plugin[]> {
|
|
35
|
+
readonly name: string;
|
|
36
|
+
readonly plugins: Plugins;
|
|
37
|
+
|
|
38
|
+
// Literals by plug-in name: the deployment file reads no environment. A value left out takes the
|
|
39
|
+
// plug-in's default.
|
|
40
|
+
readonly settings?: Readonly<Record<string, Readonly<Record<string, SettingValue>>>> | undefined;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// The typed client of one plug-in's queries. A type only, so the kit needs no oRPC.
|
|
44
|
+
export type PluginClient<Target extends Plugin> = {
|
|
45
|
+
[Query in Target['queries'][number] as Query['name']]: (
|
|
46
|
+
input: z.input<Query['input']>,
|
|
47
|
+
) => Promise<z.output<Query['output']>>;
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
export interface CheckedDeployment {
|
|
51
|
+
readonly name: string;
|
|
52
|
+
readonly sources: readonly SourceDefinition[];
|
|
53
|
+
readonly consumers: readonly ConsumerDeclaration[];
|
|
54
|
+
readonly queries: readonly AnyQueryDefinition[];
|
|
55
|
+
readonly entityKinds: readonly EntityKind[];
|
|
56
|
+
readonly linkTypes: readonly LinkTypeDeclaration[];
|
|
57
|
+
|
|
58
|
+
// the checked settings of each plug-in that declares some, with the defaults filled in
|
|
59
|
+
readonly settings: Readonly<Record<string, Readonly<Record<string, SettingValue>>>>;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export type DuplicateOf =
|
|
63
|
+
| 'plugin'
|
|
64
|
+
| 'source'
|
|
65
|
+
| 'stream'
|
|
66
|
+
| 'route'
|
|
67
|
+
| 'fact-type'
|
|
68
|
+
| 'consumer'
|
|
69
|
+
| 'table'
|
|
70
|
+
| 'query'
|
|
71
|
+
| 'link-type'
|
|
72
|
+
| 'entity-view-kind';
|
|
73
|
+
|
|
74
|
+
export type DeploymentFailure =
|
|
75
|
+
| { kind: 'kit-version'; plugin: string; kit: number }
|
|
76
|
+
|
|
77
|
+
// `link-type`: one name with two shapes. `entity-view-kind`: a kind needs one owning table.
|
|
78
|
+
| { kind: 'duplicate'; of: DuplicateOf; name: string; plugins: readonly string[] }
|
|
79
|
+
|
|
80
|
+
// a link end or a scope kind that no plug-in of the deployment brings
|
|
81
|
+
| { kind: 'unknown-entity-kind'; entityKind: EntityKind; plugin: string; usedBy: string }
|
|
82
|
+
| { kind: 'missing-consumer'; consumer: string; query: string }
|
|
83
|
+
| { kind: 'missing-stream'; stream: DeclaredStreamName; consumer: string }
|
|
84
|
+
| { kind: 'reserved-query-name'; query: string }
|
|
85
|
+
| { kind: 'reserved-table-name'; table: string; consumer: string }
|
|
86
|
+
| { kind: 'scope-reads'; consumer: string }
|
|
87
|
+
| { kind: 'reserved-source-name'; source: string }
|
|
88
|
+
|
|
89
|
+
// a value that fails the schema, or settings for a plug-in that has none or is not deployed
|
|
90
|
+
| { kind: 'invalid-settings'; plugin: string; issues: readonly string[] }
|
|
91
|
+
|
|
92
|
+
// a read or an entity view that names a table the consumer does not declare
|
|
93
|
+
| { kind: 'unknown-table'; consumer: string; table: string; usedBy: 'reads' | 'entity-view' }
|
|
94
|
+
| {
|
|
95
|
+
kind: 'unknown-column';
|
|
96
|
+
consumer: string;
|
|
97
|
+
table: string;
|
|
98
|
+
column: string;
|
|
99
|
+
usedBy: 'primary-key' | 'index' | 'entity-view';
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
export interface ReceiveRoute {
|
|
103
|
+
source: string;
|
|
104
|
+
route: string;
|
|
105
|
+
check: DeliveryCheck['kind'];
|
|
106
|
+
reach: 'public' | 'private';
|
|
107
|
+
|
|
108
|
+
// false for a route that answers and stores nothing
|
|
109
|
+
stores: boolean;
|
|
110
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { toTagged } from '@zgeoff/std';
|
|
2
|
+
import type { EntityID } from '../ids/types.ts';
|
|
3
|
+
import type { Result } from '../types.ts';
|
|
4
|
+
import type { EntityIDFailure } from './entity-id-failure.ts';
|
|
5
|
+
import type { EntityIDParts } from './entity-id-parts.ts';
|
|
6
|
+
import { findEntityIDFailure } from './find-entity-id-failure.ts';
|
|
7
|
+
|
|
8
|
+
export function buildEntityID(parts: EntityIDParts): Result<EntityID, EntityIDFailure> {
|
|
9
|
+
const failure = findEntityIDFailure(parts);
|
|
10
|
+
|
|
11
|
+
if (failure !== undefined) {
|
|
12
|
+
return { ok: false, failure };
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
return { ok: true, value: toTagged<EntityID>(`${parts.source}:${parts.kind}:${parts.id}`) };
|
|
16
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { EntityIDFailure } from './entity-id-failure.ts';
|
|
2
|
+
import type { EntityIDParts } from './entity-id-parts.ts';
|
|
3
|
+
import { factKinds } from './fact-kinds.ts';
|
|
4
|
+
|
|
5
|
+
const partPattern = /^[a-z][a-z0-9_]*$/u;
|
|
6
|
+
|
|
7
|
+
export function findEntityIDFailure(parts: EntityIDParts): EntityIDFailure | undefined {
|
|
8
|
+
if (!partPattern.test(parts.source)) {
|
|
9
|
+
return { reason: 'invalid-source' };
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
if (factKinds.some((factKind) => factKind === parts.source)) {
|
|
13
|
+
return { reason: 'reserved-source' };
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
if (!partPattern.test(parts.kind)) {
|
|
17
|
+
return { reason: 'invalid-kind' };
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
if (parts.id === '') {
|
|
21
|
+
return { reason: 'empty-id' };
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return undefined;
|
|
25
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { Result } from '../types.ts';
|
|
2
|
+
import type { EntityIDFailure } from './entity-id-failure.ts';
|
|
3
|
+
import type { EntityIDParts } from './entity-id-parts.ts';
|
|
4
|
+
import { findEntityIDFailure } from './find-entity-id-failure.ts';
|
|
5
|
+
|
|
6
|
+
export function parseEntityID(text: string): Result<EntityIDParts, EntityIDFailure> {
|
|
7
|
+
const firstSeparator = text.indexOf(':');
|
|
8
|
+
const secondSeparator = text.indexOf(':', firstSeparator + 1);
|
|
9
|
+
|
|
10
|
+
if (firstSeparator === -1 || secondSeparator === -1) {
|
|
11
|
+
return { ok: false, failure: { reason: 'malformed' } };
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const parts = {
|
|
15
|
+
source: text.slice(0, firstSeparator),
|
|
16
|
+
kind: text.slice(firstSeparator + 1, secondSeparator),
|
|
17
|
+
id: text.slice(secondSeparator + 1),
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
const failure = findEntityIDFailure(parts);
|
|
21
|
+
|
|
22
|
+
if (failure !== undefined) {
|
|
23
|
+
return { ok: false, failure };
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return { ok: true, value: parts };
|
|
27
|
+
}
|