@segmentstream-cloud/app 0.13.2 → 0.15.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/README.md +90 -15
- package/dist/app.d.ts +3 -0
- package/dist/app.js +3 -1
- package/dist/data.d.ts +10 -2
- package/dist/data.js +11 -6
- package/dist/index.d.ts +1 -0
- package/dist/jobs.d.ts +49 -0
- package/dist/jobs.js +40 -0
- package/dist/ontology.d.ts +11 -2
- package/dist/ontology.js +3 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -412,8 +412,8 @@ One authorization model covers the ontology: every `segmentstream.ontology` call
|
|
|
412
412
|
included, runs as the signed-in user through the ontology service, which checks their
|
|
413
413
|
permissions. `segmentstream.ontology.<entity>.aggregate(request)` posts to
|
|
414
414
|
`/api/ontology/<entity>/aggregate` and needs read permission on the entities it reaches.
|
|
415
|
-
`segmentstream.warehouse`
|
|
416
|
-
|
|
415
|
+
`segmentstream.warehouse` uses the workspace app credential and reaches the whole workspace
|
|
416
|
+
warehouse. The job helpers below use it for explicitly opted-in jobs. `segmentstream.fetch(path)` still requires the legacy
|
|
417
417
|
platform session. Aggregates through the service need the workspace's ontology deployed with
|
|
418
418
|
`@segmentstream-cloud/ontology` 0.13.0 or later.
|
|
419
419
|
|
|
@@ -446,17 +446,17 @@ reach. Look at or change records through `find` and actions; count, sum or compa
|
|
|
446
446
|
|
|
447
447
|
```ts
|
|
448
448
|
const { fields, rows, execution } = await segmentstream.ontology.aggregate({
|
|
449
|
-
measures: ["
|
|
450
|
-
dimensions: ["
|
|
449
|
+
measures: ["acmeSales.revenue", "acmeStock.onHand"],
|
|
450
|
+
dimensions: ["acmeProduct.assortmentStatus"],
|
|
451
451
|
filters: [
|
|
452
452
|
{
|
|
453
|
-
dimension: "
|
|
453
|
+
dimension: "acmeSales.orderDate",
|
|
454
454
|
operator: "between",
|
|
455
455
|
values: ["2026-09-01", "2026-09-30"],
|
|
456
456
|
},
|
|
457
|
-
{ dimension: "
|
|
457
|
+
{ dimension: "acmeSales.currency", operator: "equals", value: "RUB" },
|
|
458
458
|
],
|
|
459
|
-
orderBy: [{ measure: "
|
|
459
|
+
orderBy: [{ measure: "acmeSales.revenue", direction: "desc" }],
|
|
460
460
|
});
|
|
461
461
|
```
|
|
462
462
|
|
|
@@ -465,8 +465,8 @@ const { fields, rows, execution } = await segmentstream.ontology.aggregate({
|
|
|
465
465
|
`sales_2025.revenue`. This differs from the per-entity call and records, which drop it. Fields
|
|
466
466
|
and rows are keyed by these names.
|
|
467
467
|
- When an entity reaches a property's entity in more than one way, write a route from it:
|
|
468
|
-
`
|
|
469
|
-
`
|
|
468
|
+
`acmeSales.returnedProduct.brand`. Its column is still keyed by the property's own name,
|
|
469
|
+
`acmeProduct.brand`.
|
|
470
470
|
- It posts to the ontology service's `/api/ontology/aggregate` as the signed-in user, who needs
|
|
471
471
|
read permission on every entity the request reaches. Previews and local development answer
|
|
472
472
|
`aggregate_unavailable`.
|
|
@@ -486,13 +486,13 @@ Since 0.13.0, both aggregates take more conditions, buckets and a timezone:
|
|
|
486
486
|
|
|
487
487
|
```ts
|
|
488
488
|
const { rows, execution } = await segmentstream.ontology.aggregate({
|
|
489
|
-
measures: ["
|
|
490
|
-
dimensions: ["
|
|
489
|
+
measures: ["acmeSales.wonOrders"],
|
|
490
|
+
dimensions: ["acmeSales.orderDate"],
|
|
491
491
|
granularity: "week",
|
|
492
|
-
granularityOf: "
|
|
492
|
+
granularityOf: "acmeSales.orderDate",
|
|
493
493
|
filters: [
|
|
494
|
-
{ dimension: "
|
|
495
|
-
{ dimension: "
|
|
494
|
+
{ dimension: "acmeSales.orderDate", operator: "inPeriod", value: "last 4 weeks" },
|
|
495
|
+
{ dimension: "acmeProduct.brand", operator: "notIn", values: ["Unknown", "Test"] },
|
|
496
496
|
],
|
|
497
497
|
timezone: "Europe/Moscow",
|
|
498
498
|
});
|
|
@@ -519,6 +519,74 @@ const { rows, execution } = await segmentstream.ontology.aggregate({
|
|
|
519
519
|
`@segmentstream-cloud/ontology` 0.17.0 or later, whose service accepts them; earlier
|
|
520
520
|
versions refuse them. See [Upgrading to 0.13](#upgrading-to-013).
|
|
521
521
|
|
|
522
|
+
Since 0.14.0, the model-level call also takes the measures of the model, which the ontology
|
|
523
|
+
declares in `defineOntology({ measures })` over measures of several entities, by their bare
|
|
524
|
+
names beside `entity.measure` names:
|
|
525
|
+
|
|
526
|
+
```ts
|
|
527
|
+
const { fields, rows } = await segmentstream.ontology.aggregate({
|
|
528
|
+
measures: ["planAttainment", "acmeSales.ordered"],
|
|
529
|
+
dimensions: ["acmeSales.orderDate"],
|
|
530
|
+
granularity: "month",
|
|
531
|
+
granularityOf: "acmeSales.orderDate",
|
|
532
|
+
filters: [{ dimension: "acmeSales.orderDate", operator: "inPeriod", value: "this year to date" }],
|
|
533
|
+
orderBy: [{ measure: "planAttainment", direction: "desc" }],
|
|
534
|
+
});
|
|
535
|
+
// fields[1]: { name: "planAttainment", label: "Plan attainment", unit: "ratio", additive: false,
|
|
536
|
+
// filters: [...], inputs: ["acmeSales.ordered", "acmePlans.plannedUnits"] }
|
|
537
|
+
```
|
|
538
|
+
|
|
539
|
+
- A measure of the model is requested by the camelCase name the ontology declares, typed from
|
|
540
|
+
`typeof ontology`. The platform names it in snake_case, `plan_attainment`; its field and row
|
|
541
|
+
key are the camelCase form, `planAttainment`, as for entity measures.
|
|
542
|
+
- Its field carries `inputs`, the entity measures it reads, as `entity.measure` in camelCase per
|
|
543
|
+
segment, like other names. They are computed whether or not you request them; request one to
|
|
544
|
+
get its column too.
|
|
545
|
+
- Its `filters` lists the filters that applied to every input. A filter on a property the
|
|
546
|
+
measure aligns applies to each entity through its own property; a filter that reaches some of
|
|
547
|
+
its entities and not others is refused with `invalid_aggregate_query`.
|
|
548
|
+
- A group one of its entities has no rows for, or a zero denominator, gives `null`.
|
|
549
|
+
- The caller needs read permission on every entity it reads.
|
|
550
|
+
- These requests need the workspace's ontology deployed with `@segmentstream-cloud/ontology`
|
|
551
|
+
0.23.0 or later, which declares and serves measures of the model; earlier versions refuse
|
|
552
|
+
them. Requests that name none work as before, and their fields carry no `inputs`.
|
|
553
|
+
|
|
554
|
+
## Start a pipeline job
|
|
555
|
+
|
|
556
|
+
With app SDK 0.15.0 and pipeline SDK 0.1.0a65 or later, opt in a job and deploy it:
|
|
557
|
+
|
|
558
|
+
```python
|
|
559
|
+
s.job("land_watch", assets=[land_checks], allow_app_runs=True)
|
|
560
|
+
```
|
|
561
|
+
|
|
562
|
+
In server code, check the user's role or permissions before calling on their behalf:
|
|
563
|
+
|
|
564
|
+
```ts
|
|
565
|
+
const user = await segmentstream.requireUser();
|
|
566
|
+
if (!user.roles.includes("operator")) {
|
|
567
|
+
throw new Error("An operator role is required.");
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
const run = await segmentstream.jobs.run("land_watch");
|
|
571
|
+
// From a later authorized request, for example a Route Handler the page polls:
|
|
572
|
+
const current = await segmentstream.jobs.getRun(run.id);
|
|
573
|
+
const recent = await segmentstream.jobs.listRuns("land_watch");
|
|
574
|
+
```
|
|
575
|
+
|
|
576
|
+
The helpers use the workspace app credential, read on every call, or fall back to the
|
|
577
|
+
legacy member session. Credential access defaults to denied unless the current deployed
|
|
578
|
+
job sets `allow_app_runs=True`. It stays within the workspace and permits submission and
|
|
579
|
+
status/history reads; run logs, activity and cancellation require platform membership.
|
|
580
|
+
Removing the flag revokes app access to that job's runs.
|
|
581
|
+
|
|
582
|
+
`run()` returns immediately with a run ID and status. It generates a UUID idempotency key
|
|
583
|
+
unless passed `{ idempotencyKey }`; reuse an explicit key when retrying one button action.
|
|
584
|
+
Separate calls with new keys can run concurrently. Partitioned jobs accept
|
|
585
|
+
`{ partitionRange: { startDate, endDate } }`. All helpers accept an optional `signal`.
|
|
586
|
+
Poll `getRun()` until status is `succeeded`, `failed` or `cancelled`, and tolerate future
|
|
587
|
+
status values. Protect both submission and status routes with your app's authorization.
|
|
588
|
+
See [app data access](../../docs/app-data.md#start-a-pipeline-job-from-the-app) for a Route Handler.
|
|
589
|
+
|
|
522
590
|
## How requests are handled
|
|
523
591
|
|
|
524
592
|
- Page navigations without a session are redirected to `/sign-in?returnTo=<path>` and
|
|
@@ -534,7 +602,7 @@ const { rows, execution } = await segmentstream.ontology.aggregate({
|
|
|
534
602
|
| -------------------------------------- | ------------------------------------------------------------------------------- |
|
|
535
603
|
| `NEON_AUTH_BASE_URL` | The workspace's Neon Auth URL. Set: sign in with Neon Auth. Unset: 0.2 sign-in. |
|
|
536
604
|
| `SEGMENTSTREAM_APP_AUTH_COOKIE_SECRET` | 32 or more characters; signs the session cache cookie. Required with Neon Auth. |
|
|
537
|
-
| `SEGMENTSTREAM_APP_CREDENTIAL` | The workspace app credential for warehouse queries.
|
|
605
|
+
| `SEGMENTSTREAM_APP_CREDENTIAL` | The workspace app credential for warehouse queries and opted-in pipeline jobs. |
|
|
538
606
|
| `SEGMENTSTREAM_ONTOLOGY_URL` | Optional: the ontology service when it does not run beside the app. |
|
|
539
607
|
| `SEGMENTSTREAM_URL` | SegmentStream origin. Defaults to `https://cloud.segmentstream.com`. |
|
|
540
608
|
|
|
@@ -629,3 +697,10 @@ Replace segmentstream.semantic with typed warehouse-backed entity aggregates. Se
|
|
|
629
697
|
- The new operators, `inPeriod`, granularities and `timezone` need the workspace's ontology
|
|
630
698
|
deployed with `@segmentstream-cloud/ontology` 0.17.0 or later. Requests that use none of
|
|
631
699
|
them work as before.
|
|
700
|
+
|
|
701
|
+
## Upgrading to 0.14
|
|
702
|
+
|
|
703
|
+
- `segmentstream.ontology.aggregate()` accepts the measures of the model by their bare names, typed
|
|
704
|
+
from an ontology that declares them with `defineOntology({ measures })`. Ontologies without them
|
|
705
|
+
type the same names as before.
|
|
706
|
+
- `AggregateField` gains `inputs`, which only fields of measures of the model carry.
|
package/dist/app.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { Environment, SegmentStreamAppConfig } from "./config.js";
|
|
2
2
|
import type { AgentIdentities } from "./agent.js";
|
|
3
3
|
import type { WarehouseClient } from "./data.js";
|
|
4
|
+
import type { JobsClient } from "./jobs.js";
|
|
4
5
|
import type { SegmentStreamRouteHandlers } from "./handlers.js";
|
|
5
6
|
import type { AgentAuthorizations } from "./oauth.js";
|
|
6
7
|
import type { AuthRouteHandlers, CreateNeonAuth, SignOutOptions } from "./neon.js";
|
|
@@ -46,6 +47,8 @@ export interface SegmentStreamApp<O = unknown> {
|
|
|
46
47
|
* (`SEGMENTSTREAM_APP_CREDENTIAL`) when it is set, else the signed-in member's session.
|
|
47
48
|
*/
|
|
48
49
|
readonly warehouse: WarehouseClient;
|
|
50
|
+
/** Start and read pipeline runs with the workspace credential. Server-only; gate app users. */
|
|
51
|
+
readonly jobs: JobsClient;
|
|
49
52
|
/** The app's ontology service, `/api/ontology`, as the signed-in user. Server-only. */
|
|
50
53
|
readonly ontology: OntologyClient<O>;
|
|
51
54
|
/**
|
package/dist/app.js
CHANGED
|
@@ -57,6 +57,7 @@ export function createApp(config, dependencies = {}) {
|
|
|
57
57
|
getAccessToken: legacy.getAccessToken,
|
|
58
58
|
fetch: data.fetch,
|
|
59
59
|
warehouse: data.warehouse,
|
|
60
|
+
jobs: data.jobs,
|
|
60
61
|
ontology: createOntologyClient(ontologyTransport),
|
|
61
62
|
oauth: createAgentAuthorizations(ontologyTransport),
|
|
62
63
|
agent: createAgentIdentities(ontologyTransport),
|
|
@@ -87,6 +88,7 @@ export function createApp(config, dependencies = {}) {
|
|
|
87
88
|
getAccessToken: async () => null,
|
|
88
89
|
fetch: data.fetch,
|
|
89
90
|
warehouse: data.warehouse,
|
|
91
|
+
jobs: data.jobs,
|
|
90
92
|
ontology: createOntologyClient(neon.ontologyTransport),
|
|
91
93
|
oauth: createAgentAuthorizations(neon.ontologyTransport),
|
|
92
94
|
agent: createAgentIdentities(neon.ontologyTransport),
|
|
@@ -97,7 +99,7 @@ async function noSegmentStreamSession() {
|
|
|
97
99
|
throw new SegmentStreamApiError({
|
|
98
100
|
code: "app_credential_missing",
|
|
99
101
|
status: 401,
|
|
100
|
-
detail: "Apps that sign in with Neon Auth have no SegmentStream session. Warehouse queries
|
|
102
|
+
detail: "Apps that sign in with Neon Auth have no SegmentStream session. Warehouse queries and opted-in job runs need SEGMENTSTREAM_APP_CREDENTIAL, which SegmentStream sets on deploy. Generic fetch requires a legacy SegmentStream session.",
|
|
101
103
|
});
|
|
102
104
|
}
|
|
103
105
|
function notConfiguredHandlers() {
|
package/dist/data.d.ts
CHANGED
|
@@ -115,7 +115,8 @@ export type ModelAggregateFilter<Dimension extends string = string> = {
|
|
|
115
115
|
readonly values?: undefined;
|
|
116
116
|
};
|
|
117
117
|
/**
|
|
118
|
-
* A model-level aggregate: measures of one or more entities, as `entity.measure`,
|
|
118
|
+
* A model-level aggregate: measures of one or more entities, as `entity.measure`, and measures of
|
|
119
|
+
* the model, declared in `defineOntology({ measures })`, by their bare names, grouped by
|
|
119
120
|
* `entity.property` dimensions. A dimension may instead be a route from a measure's entity,
|
|
120
121
|
* `entity.relationship.property`, which picks the way that entity reaches the property. Date bounds
|
|
121
122
|
* are `between` or `inPeriod` filters.
|
|
@@ -156,6 +157,12 @@ export interface AggregateField {
|
|
|
156
157
|
* rows snapshot. The request groups by it or filters it to one day.
|
|
157
158
|
*/
|
|
158
159
|
readonly balanceOver?: string;
|
|
160
|
+
/**
|
|
161
|
+
* On a measure of the model: the entity measures it reads, as `entity.measure`, in declaration
|
|
162
|
+
* order. They are computed whether or not the request names them, and come back as columns only
|
|
163
|
+
* when it does. Its `filters` are the filters that applied to every one of them.
|
|
164
|
+
*/
|
|
165
|
+
readonly inputs?: readonly string[];
|
|
159
166
|
}
|
|
160
167
|
export interface AggregateResult<Row = Record<string, string | boolean | null>> {
|
|
161
168
|
readonly deploymentId: string | null;
|
|
@@ -207,7 +214,7 @@ export interface DataClientDependencies {
|
|
|
207
214
|
readonly getAccessToken: () => Promise<string | null>;
|
|
208
215
|
/**
|
|
209
216
|
* The workspace app credential, `SEGMENTSTREAM_APP_CREDENTIAL`, read on every call. The API
|
|
210
|
-
* accepts it for warehouse queries and
|
|
217
|
+
* accepts it for warehouse queries and opted-in job runs; otherwise they use the session token.
|
|
211
218
|
*/
|
|
212
219
|
readonly getAppCredential?: () => string | undefined;
|
|
213
220
|
readonly sleep?: (ms: number, signal?: AbortSignal) => Promise<void>;
|
|
@@ -217,4 +224,5 @@ export interface DataClientDependencies {
|
|
|
217
224
|
export declare function createDataClient(dependencies: DataClientDependencies): {
|
|
218
225
|
fetch: (path: string, init?: RequestInit) => Promise<Response>;
|
|
219
226
|
warehouse: WarehouseClient;
|
|
227
|
+
jobs: import("./jobs.js").JobsClient;
|
|
220
228
|
};
|
package/dist/data.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { SegmentStreamApiError, readJson } from "./errors.js";
|
|
2
2
|
import { encodeParameters } from "./parameters.js";
|
|
3
|
+
import { createJobsClient } from "./jobs.js";
|
|
3
4
|
export { SegmentStreamApiError } from "./errors.js";
|
|
4
5
|
const REQUEST_TIMEOUT_MS = 45_000;
|
|
5
6
|
const DEFAULT_MAX_ROWS = 10_000;
|
|
@@ -17,8 +18,8 @@ export function createDataClient(dependencies) {
|
|
|
17
18
|
function apiFetch(path, init = {}) {
|
|
18
19
|
return authorizedFetch(path, init, dependencies.getAccessToken);
|
|
19
20
|
}
|
|
20
|
-
/**
|
|
21
|
-
async function
|
|
21
|
+
/** Workspace operations prefer the app credential, which needs no SegmentStream session. */
|
|
22
|
+
async function workspaceToken() {
|
|
22
23
|
return dependencies.getAppCredential?.() ?? dependencies.getAccessToken();
|
|
23
24
|
}
|
|
24
25
|
async function authorizedFetch(path, init, getToken) {
|
|
@@ -122,7 +123,7 @@ export function createDataClient(dependencies) {
|
|
|
122
123
|
parameters: encodeParameters(options.params),
|
|
123
124
|
...bytesLimit(options.maximumBytesBilled),
|
|
124
125
|
pageSize: bounds.pageSize,
|
|
125
|
-
}, options.signal,
|
|
126
|
+
}, options.signal, workspaceToken);
|
|
126
127
|
return collect(page, bounds, options.signal);
|
|
127
128
|
}
|
|
128
129
|
async function result(queryId, options = {}) {
|
|
@@ -135,7 +136,7 @@ export function createDataClient(dependencies) {
|
|
|
135
136
|
if (pageToken !== undefined) {
|
|
136
137
|
search.set("pageToken", pageToken);
|
|
137
138
|
}
|
|
138
|
-
return call("GET", `${workspace}/warehouse/queries/${encodeURIComponent(queryId)}?${search.toString()}`, undefined, signal,
|
|
139
|
+
return call("GET", `${workspace}/warehouse/queries/${encodeURIComponent(queryId)}?${search.toString()}`, undefined, signal, workspaceToken);
|
|
139
140
|
}
|
|
140
141
|
const warehouse = {
|
|
141
142
|
query,
|
|
@@ -146,7 +147,7 @@ export function createDataClient(dependencies) {
|
|
|
146
147
|
parameters: encodeParameters(options.params),
|
|
147
148
|
...bytesLimit(options.maximumBytesBilled),
|
|
148
149
|
dryRun: true,
|
|
149
|
-
}, options.signal,
|
|
150
|
+
}, options.signal, workspaceToken);
|
|
150
151
|
return {
|
|
151
152
|
columns: page.columns,
|
|
152
153
|
bytesProcessed: page.bytesProcessed ?? null,
|
|
@@ -154,7 +155,11 @@ export function createDataClient(dependencies) {
|
|
|
154
155
|
};
|
|
155
156
|
},
|
|
156
157
|
};
|
|
157
|
-
|
|
158
|
+
const jobs = createJobsClient({
|
|
159
|
+
workspace,
|
|
160
|
+
fetch: (path, init) => authorizedFetch(path, init, workspaceToken),
|
|
161
|
+
});
|
|
162
|
+
return { fetch: apiFetch, warehouse, jobs };
|
|
158
163
|
}
|
|
159
164
|
function bytesLimit(value) {
|
|
160
165
|
if (value === undefined) {
|
package/dist/index.d.ts
CHANGED
|
@@ -24,6 +24,7 @@ export { AGENT_CONNECT_HANDLER, AGENT_CONNECT_PAGE } from "./agent.js";
|
|
|
24
24
|
export type { AgentIdentities, AgentIdentity, AgentPairing } from "./agent.js";
|
|
25
25
|
export type { AgentAuthorizationRequest, AgentAuthorizations, AgentScope } from "./oauth.js";
|
|
26
26
|
export type { ApiIssue } from "./errors.js";
|
|
27
|
+
export type { JobReadOptions, JobRun, JobRunOptions, JobsClient } from "./jobs.js";
|
|
27
28
|
export type { CurrentUser } from "./me.js";
|
|
28
29
|
export type { AuthRouteHandler, AuthRouteHandlers, SignOutOptions } from "./neon.js";
|
|
29
30
|
export type { ActionCall, ActionOptions, ActionResponse, Change, Edit, EditKind, EntityClient, FieldFilter, GeneratedActions, HistoryEntry, HistoryOptions, HistoryPage, JsonRecord, JsonValue, ManyFilter, ModelAggregateClient, OntologyClient, AggregateEntityClient, OntologyPage, OntologyQuery, OrderBy, RequestOptions, UntypedOntologyClient, ValidationIssue, Where, } from "./ontology.js";
|
package/dist/jobs.d.ts
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/** A pipeline run, returned immediately by `run()` and refreshed with `getRun()`. */
|
|
2
|
+
export interface JobRun {
|
|
3
|
+
readonly id: string;
|
|
4
|
+
readonly deploymentId: string;
|
|
5
|
+
readonly jobName: string;
|
|
6
|
+
/** `queued`, `starting`, `running`, `succeeded`, `failed` or `cancelled`; future values are accepted. */
|
|
7
|
+
readonly status: string;
|
|
8
|
+
readonly trigger: string;
|
|
9
|
+
readonly partitionRange?: {
|
|
10
|
+
readonly startDate: string;
|
|
11
|
+
readonly endDate: string;
|
|
12
|
+
};
|
|
13
|
+
readonly failure?: {
|
|
14
|
+
readonly code: string;
|
|
15
|
+
readonly detail: string;
|
|
16
|
+
readonly retryable: boolean;
|
|
17
|
+
readonly reference: string;
|
|
18
|
+
};
|
|
19
|
+
readonly createdAt: string;
|
|
20
|
+
readonly updatedAt: string;
|
|
21
|
+
readonly startedAt?: string;
|
|
22
|
+
readonly completedAt?: string;
|
|
23
|
+
}
|
|
24
|
+
export interface JobRunOptions {
|
|
25
|
+
/** Reuse the same key when retrying one submission. Defaults to a new UUID for each call. */
|
|
26
|
+
readonly idempotencyKey?: string;
|
|
27
|
+
readonly partitionRange?: {
|
|
28
|
+
readonly startDate: string;
|
|
29
|
+
readonly endDate: string;
|
|
30
|
+
};
|
|
31
|
+
readonly signal?: AbortSignal;
|
|
32
|
+
}
|
|
33
|
+
export interface JobReadOptions {
|
|
34
|
+
readonly signal?: AbortSignal;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Server-only pipeline execution. The workspace credential can start and read only jobs deployed
|
|
38
|
+
* with `allow_app_runs=True`. Check the app user's permissions before calling on their behalf.
|
|
39
|
+
*/
|
|
40
|
+
export interface JobsClient {
|
|
41
|
+
/** Submit a run without waiting for completion. */
|
|
42
|
+
readonly run: (jobName: string, options?: JobRunOptions) => Promise<JobRun>;
|
|
43
|
+
readonly getRun: (runId: string, options?: JobReadOptions) => Promise<JobRun>;
|
|
44
|
+
readonly listRuns: (jobName: string, options?: JobReadOptions) => Promise<readonly JobRun[]>;
|
|
45
|
+
}
|
|
46
|
+
export declare function createJobsClient(dependencies: {
|
|
47
|
+
readonly workspace: string;
|
|
48
|
+
readonly fetch: (path: string, init: RequestInit) => Promise<Response>;
|
|
49
|
+
}): JobsClient;
|
package/dist/jobs.js
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { apiErrorFrom, readJson } from "./errors.js";
|
|
2
|
+
export function createJobsClient(dependencies) {
|
|
3
|
+
async function request(path, init) {
|
|
4
|
+
const response = await dependencies.fetch(path, init);
|
|
5
|
+
const payload = await readJson(response);
|
|
6
|
+
if (!response.ok) {
|
|
7
|
+
throw apiErrorFrom(response.status, payload, "SegmentStream rejected the job request.");
|
|
8
|
+
}
|
|
9
|
+
return payload;
|
|
10
|
+
}
|
|
11
|
+
return {
|
|
12
|
+
async run(jobName, options = {}) {
|
|
13
|
+
const key = options.idempotencyKey?.trim() ?? crypto.randomUUID();
|
|
14
|
+
if (!/^[A-Za-z0-9._:-]{8,128}$/.test(key)) {
|
|
15
|
+
throw new TypeError("idempotencyKey must be 8–128 letters, numbers, dots, underscores, colons or hyphens.");
|
|
16
|
+
}
|
|
17
|
+
const { run } = await request(`${dependencies.workspace}/jobs/${segment(jobName, "jobName")}/runs`, {
|
|
18
|
+
method: "POST",
|
|
19
|
+
headers: { "content-type": "application/json", "idempotency-key": key },
|
|
20
|
+
body: JSON.stringify(options.partitionRange ? { partitionRange: options.partitionRange } : {}),
|
|
21
|
+
signal: options.signal,
|
|
22
|
+
});
|
|
23
|
+
return run;
|
|
24
|
+
},
|
|
25
|
+
async getRun(runId, options = {}) {
|
|
26
|
+
const { run } = await request(`${dependencies.workspace}/runs/${segment(runId, "runId")}?includeTrigger=true`, { method: "GET", signal: options.signal });
|
|
27
|
+
return run;
|
|
28
|
+
},
|
|
29
|
+
async listRuns(jobName, options = {}) {
|
|
30
|
+
const { runs } = await request(`${dependencies.workspace}/jobs/${segment(jobName, "jobName")}/runs?includeTrigger=true`, { method: "GET", signal: options.signal });
|
|
31
|
+
return runs;
|
|
32
|
+
},
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
function segment(value, name) {
|
|
36
|
+
if (!value || value === "." || value === "..") {
|
|
37
|
+
throw new TypeError(`${name} must be a non-empty identifier.`);
|
|
38
|
+
}
|
|
39
|
+
return encodeURIComponent(value);
|
|
40
|
+
}
|
package/dist/ontology.d.ts
CHANGED
|
@@ -252,7 +252,16 @@ type Digit = "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9";
|
|
|
252
252
|
*/
|
|
253
253
|
type CamelCase<S extends string> = S extends `${infer Head}_${infer Tail}` ? Tail extends `${Digit}${string}` ? `${Head}_${CamelCase<Tail>}` : `${Head}${Capitalize<CamelCase<Tail>>}` : S;
|
|
254
254
|
/** `entity.measure` for every measure of every entity, warehouse-backed or stored. */
|
|
255
|
-
type
|
|
255
|
+
type EntityMeasuresOf<E> = E extends unknown ? `${CamelCase<NameOf<E>>}.${MeasuresOf<E>}` : never;
|
|
256
|
+
/**
|
|
257
|
+
* The measures of the model, `defineOntology({ measures })`, by their bare declared names. An
|
|
258
|
+
* ontology SDK without them, or a definition typed as any record of measures, names none.
|
|
259
|
+
*/
|
|
260
|
+
type ModelMeasuresOf<O> = O extends {
|
|
261
|
+
readonly config: {
|
|
262
|
+
readonly measures?: infer M;
|
|
263
|
+
};
|
|
264
|
+
} ? DeclaredNames<NonNullable<M>> : never;
|
|
256
265
|
/** A route picks how an entity with measures reaches a property, so only such entities start one. */
|
|
257
266
|
type RoutesOf<E, O> = [MeasuresOf<E>] extends [never] ? never : PathsOf<E, O>;
|
|
258
267
|
/**
|
|
@@ -275,7 +284,7 @@ type TypedOntologyClient<O> = {
|
|
|
275
284
|
readonly [N in NameOf<EntitiesOf<O>>]: ClientOf<Extract<EntitiesOf<O>, {
|
|
276
285
|
readonly name: N;
|
|
277
286
|
}>, O>;
|
|
278
|
-
} & ModelAggregateClient<
|
|
287
|
+
} & ModelAggregateClient<EntityMeasuresOf<EntitiesOf<O>> | ModelMeasuresOf<O>, ModelDimensionsOf<EntitiesOf<O>, O>>;
|
|
279
288
|
/** A client for any entity, when the app passes no ontology type. */
|
|
280
289
|
export type UntypedOntologyClient = ModelAggregateClient & {
|
|
281
290
|
readonly [entity: string]: EntityClient<JsonRecord, {
|
package/dist/ontology.js
CHANGED
|
@@ -247,8 +247,8 @@ function aggregateToWire(request) {
|
|
|
247
247
|
};
|
|
248
248
|
}
|
|
249
249
|
/**
|
|
250
|
-
* Field names, the filters each measure applied, a balance's dimension
|
|
251
|
-
* camelCase per segment; `execution` is passed on as sent.
|
|
250
|
+
* Field names, the filters each measure applied, a balance's dimension, a model measure's inputs
|
|
251
|
+
* and row keys come back camelCase per segment; `execution` is passed on as sent.
|
|
252
252
|
*/
|
|
253
253
|
function aggregateResultFromWire(result, decodeName) {
|
|
254
254
|
return {
|
|
@@ -258,6 +258,7 @@ function aggregateResultFromWire(result, decodeName) {
|
|
|
258
258
|
name: decodeName(field.name),
|
|
259
259
|
...(field.filters === undefined ? {} : { filters: field.filters.map(decodeName) }),
|
|
260
260
|
...(field.balanceOver === undefined ? {} : { balanceOver: decodeName(field.balanceOver) }),
|
|
261
|
+
...(field.inputs === undefined ? {} : { inputs: field.inputs.map(decodeName) }),
|
|
261
262
|
})),
|
|
262
263
|
rows: result.rows.map((row) => Object.fromEntries(Object.entries(row).map(([name, value]) => [decodeName(name), value]))),
|
|
263
264
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@segmentstream-cloud/app",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.15.0",
|
|
4
4
|
"description": "Sign-in, users, permissions, the ontology client and warehouse data access for SegmentStream workspace apps built with Next.js.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "SegmentStream",
|