@clidey/whodb-sdk 0.0.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 +81 -0
- package/dist/auth.d.ts +26 -0
- package/dist/auth.js +65 -0
- package/dist/client.d.ts +51 -0
- package/dist/client.js +157 -0
- package/dist/config.d.ts +37 -0
- package/dist/config.js +39 -0
- package/dist/dataset.d.ts +26 -0
- package/dist/dataset.js +45 -0
- package/dist/errors.d.ts +43 -0
- package/dist/errors.js +59 -0
- package/dist/files.d.ts +25 -0
- package/dist/files.js +53 -0
- package/dist/generated/hydration.d.ts +3 -0
- package/dist/generated/hydration.js +32 -0
- package/dist/generated/manifest.d.ts +12 -0
- package/dist/generated/manifest.js +80 -0
- package/dist/generated/operations.d.ts +196 -0
- package/dist/generated/operations.js +145 -0
- package/dist/generated/surface.d.ts +190 -0
- package/dist/generated/surface.js +191 -0
- package/dist/generated/types.d.ts +302 -0
- package/dist/generated/types.js +2 -0
- package/dist/hydrate.d.ts +18 -0
- package/dist/hydrate.js +83 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.js +9 -0
- package/dist/manifest-check.d.ts +15 -0
- package/dist/manifest-check.js +43 -0
- package/dist/ontology.d.ts +73 -0
- package/dist/ontology.js +240 -0
- package/dist/pagination.d.ts +21 -0
- package/dist/pagination.js +33 -0
- package/dist/source.d.ts +31 -0
- package/dist/source.js +63 -0
- package/dist/transport-http.d.ts +22 -0
- package/dist/transport-http.js +60 -0
- package/dist/transport-ipc.d.ts +31 -0
- package/dist/transport-ipc.js +183 -0
- package/dist/transport.d.ts +9 -0
- package/dist/transport.js +1 -0
- package/dist/version.d.ts +5 -0
- package/dist/version.js +5 -0
- package/package.json +34 -0
package/dist/hydrate.js
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { hydrationRules, hydrationDefault } from './generated/hydration.js';
|
|
2
|
+
/** Coerces one stringly-typed cell into its native type per the shared rules. */
|
|
3
|
+
export function coerceValue(raw, columnType) {
|
|
4
|
+
if (raw === null || raw === undefined)
|
|
5
|
+
return null;
|
|
6
|
+
const kind = hydrationRules[columnType.toLowerCase()] ?? hydrationDefault;
|
|
7
|
+
switch (kind) {
|
|
8
|
+
case 'int': {
|
|
9
|
+
const parsed = Number.parseInt(raw, 10);
|
|
10
|
+
return Number.isNaN(parsed) ? raw : parsed;
|
|
11
|
+
}
|
|
12
|
+
case 'float': {
|
|
13
|
+
const parsed = Number.parseFloat(raw);
|
|
14
|
+
return Number.isNaN(parsed) ? raw : parsed;
|
|
15
|
+
}
|
|
16
|
+
case 'bool':
|
|
17
|
+
return raw === 'true' || raw === 't' || raw === '1';
|
|
18
|
+
case 'timestamp':
|
|
19
|
+
case 'date': {
|
|
20
|
+
const parsed = new Date(raw);
|
|
21
|
+
return Number.isNaN(parsed.getTime()) ? raw : parsed;
|
|
22
|
+
}
|
|
23
|
+
case 'json': {
|
|
24
|
+
try {
|
|
25
|
+
return JSON.parse(raw);
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
return raw;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
default:
|
|
32
|
+
return raw;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Normalizes the two wire result shapes to one:
|
|
37
|
+
* - DatasetQueryResult: { columns: string[] (names only), rows, total }
|
|
38
|
+
* - RowsResult (CE-derived): { Columns: {Name,Type}[], Rows, TotalCount }
|
|
39
|
+
* DatasetQueryResult carries no column types — coercion for it comes from
|
|
40
|
+
* ontology property metadata (dataType), falling back to string.
|
|
41
|
+
*/
|
|
42
|
+
function normalize(result) {
|
|
43
|
+
const any = result;
|
|
44
|
+
if (Array.isArray(any.columns)) {
|
|
45
|
+
return {
|
|
46
|
+
columns: any.columns.map(name => ({ name, type: '' })),
|
|
47
|
+
rows: any.rows ?? [],
|
|
48
|
+
totalCount: any.total ?? null,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
return {
|
|
52
|
+
columns: (any.Columns ?? []).map(c => ({ name: c.Name, type: c.Type })),
|
|
53
|
+
rows: (any.Rows ?? []),
|
|
54
|
+
totalCount: any.TotalCount ?? null,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
/** Builds a property-type map from ontology entity metadata. */
|
|
58
|
+
export function propertyTypesOf(entity) {
|
|
59
|
+
const map = new Map();
|
|
60
|
+
for (const property of entity.properties ?? []) {
|
|
61
|
+
if (property?.apiName && property?.dataType) {
|
|
62
|
+
map.set(property.apiName, property.dataType);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return map;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Hydrates a stringly-typed result into native-typed row objects. Ontology
|
|
69
|
+
* property metadata, when supplied, overrides the wire column type — the
|
|
70
|
+
* ontology's dataType is more precise than the storage column type.
|
|
71
|
+
*/
|
|
72
|
+
export function hydrateRows(result, propertyTypes) {
|
|
73
|
+
const { columns, rows, totalCount } = normalize(result);
|
|
74
|
+
const hydrated = rows.map(cells => {
|
|
75
|
+
const row = {};
|
|
76
|
+
columns.forEach((column, index) => {
|
|
77
|
+
const type = propertyTypes?.get(column.name) ?? column.type;
|
|
78
|
+
row[column.name] = coerceValue(cells[index] ?? null, type);
|
|
79
|
+
});
|
|
80
|
+
return row;
|
|
81
|
+
});
|
|
82
|
+
return { rows: hydrated, totalCount: totalCount ?? null };
|
|
83
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export { WhoDB } from './client.js';
|
|
2
|
+
export type { WhoDBConfig } from './config.js';
|
|
3
|
+
export type { Transport } from './transport.js';
|
|
4
|
+
export { IpcTransport } from './transport-ipc.js';
|
|
5
|
+
export type { CredentialProvider } from './auth.js';
|
|
6
|
+
export { apiKeyProvider, tokenProvider, cliProvider } from './auth.js';
|
|
7
|
+
export { WhoDBError, AuthError, NotFoundError, ValidationError, WhoDBVersionError, CliCredentialsError, TransportCapabilityError, PlatformError, } from './errors.js';
|
|
8
|
+
export type { Row } from './hydrate.js';
|
|
9
|
+
export type { Page } from './pagination.js';
|
|
10
|
+
export { ListCall } from './pagination.js';
|
|
11
|
+
export { OntologyHandle } from './ontology.js';
|
|
12
|
+
export { DatasetHandle } from './dataset.js';
|
|
13
|
+
export { SourceHandle } from './source.js';
|
|
14
|
+
export { FilesHandle } from './files.js';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export { WhoDB } from './client.js';
|
|
2
|
+
export { IpcTransport } from './transport-ipc.js';
|
|
3
|
+
export { apiKeyProvider, tokenProvider, cliProvider } from './auth.js';
|
|
4
|
+
export { WhoDBError, AuthError, NotFoundError, ValidationError, WhoDBVersionError, CliCredentialsError, TransportCapabilityError, PlatformError, } from './errors.js';
|
|
5
|
+
export { ListCall } from './pagination.js';
|
|
6
|
+
export { OntologyHandle } from './ontology.js';
|
|
7
|
+
export { DatasetHandle } from './dataset.js';
|
|
8
|
+
export { SourceHandle } from './source.js';
|
|
9
|
+
export { FilesHandle } from './files.js';
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Emits the two-tier versioning-policy warnings for an operation, once per
|
|
3
|
+
* process (SDK_DESIGN.md §2.3): deprecated → upgrade-before-sunset warning;
|
|
4
|
+
* behaviorChanged → semantics-changed warning. Actually-removed operations
|
|
5
|
+
* surface as WhoDBVersionError from interpretServerError below.
|
|
6
|
+
*/
|
|
7
|
+
export declare function warnIfFlagged(operationName: string): void;
|
|
8
|
+
/**
|
|
9
|
+
* Detects the server rejecting an operation this SDK was generated with —
|
|
10
|
+
* the operation was removed after this SDK release. Converts the low-level
|
|
11
|
+
* validation error into the actionable upgrade error.
|
|
12
|
+
*/
|
|
13
|
+
export declare function interpretServerError(error: unknown, sdkVersion: string): unknown;
|
|
14
|
+
/** Test hook: clears the once-per-process warning memory. */
|
|
15
|
+
export declare function resetWarnings(): void;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { embeddedManifest } from './generated/manifest.js';
|
|
2
|
+
import { WhoDBVersionError } from './errors.js';
|
|
3
|
+
const warned = new Set();
|
|
4
|
+
/**
|
|
5
|
+
* Emits the two-tier versioning-policy warnings for an operation, once per
|
|
6
|
+
* process (SDK_DESIGN.md §2.3): deprecated → upgrade-before-sunset warning;
|
|
7
|
+
* behaviorChanged → semantics-changed warning. Actually-removed operations
|
|
8
|
+
* surface as WhoDBVersionError from interpretServerError below.
|
|
9
|
+
*/
|
|
10
|
+
export function warnIfFlagged(operationName) {
|
|
11
|
+
const entry = embeddedManifest[operationName];
|
|
12
|
+
if (!entry || warned.has(operationName))
|
|
13
|
+
return;
|
|
14
|
+
if (entry.deprecated) {
|
|
15
|
+
warned.add(operationName);
|
|
16
|
+
console.warn(`[whodb] ${operationName} is deprecated${entry.sunsetAt ? ` and will be removed after ${entry.sunsetAt}` : ''} — upgrade @clidey/whodb-sdk before then.${entry.note ? ` ${entry.note}` : ''}`);
|
|
17
|
+
}
|
|
18
|
+
else if (entry.behaviorChanged) {
|
|
19
|
+
warned.add(operationName);
|
|
20
|
+
console.warn(`[whodb] ${operationName}'s behavior changed in this platform release — results may differ from previous SDK versions.${entry.note ? ` ${entry.note}` : ''}`);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
const UNKNOWN_OPERATION_PATTERNS = [
|
|
24
|
+
/Cannot query field/i,
|
|
25
|
+
/Unknown field/i,
|
|
26
|
+
/Unknown type/i,
|
|
27
|
+
/has no field/i,
|
|
28
|
+
];
|
|
29
|
+
/**
|
|
30
|
+
* Detects the server rejecting an operation this SDK was generated with —
|
|
31
|
+
* the operation was removed after this SDK release. Converts the low-level
|
|
32
|
+
* validation error into the actionable upgrade error.
|
|
33
|
+
*/
|
|
34
|
+
export function interpretServerError(error, sdkVersion) {
|
|
35
|
+
if (error instanceof Error && UNKNOWN_OPERATION_PATTERNS.some(p => p.test(error.message))) {
|
|
36
|
+
return new WhoDBVersionError(`this SDK (${sdkVersion}) was built for an older WhoDB platform API; upgrade the @clidey/whodb-sdk package`);
|
|
37
|
+
}
|
|
38
|
+
return error;
|
|
39
|
+
}
|
|
40
|
+
/** Test hook: clears the once-per-process warning memory. */
|
|
41
|
+
export function resetWarnings() {
|
|
42
|
+
warned.clear();
|
|
43
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import type { Transport } from './transport.js';
|
|
2
|
+
import type { OntologyDescription, OntologyFastLookup, OntologyObjectType, OntologyStatsResult, OntologySimilarInput, OntologySimilarityResult, OntologyQueryInput, OntologyQuerySortInput, OntologyAggregateMetricInput, WhereCondition, SortCondition, OntologyAddRowsResult } from './generated/types.js';
|
|
3
|
+
import { type Row } from './hydrate.js';
|
|
4
|
+
import { ListCall } from './pagination.js';
|
|
5
|
+
/** Options for list-shaped ontology reads. `where` is a JSON filter object
|
|
6
|
+
* (property → { eq/gt/lt/in/... }) serialized into OntologyQuery.whereJson. */
|
|
7
|
+
export interface ListOptions {
|
|
8
|
+
where?: Record<string, unknown>;
|
|
9
|
+
sort?: OntologyQuerySortInput[];
|
|
10
|
+
pageSize?: number;
|
|
11
|
+
}
|
|
12
|
+
/** Options for the flexible query surface (search, joins, grouping). */
|
|
13
|
+
export interface QueryOptions extends Omit<OntologyQueryInput, 'entity'> {
|
|
14
|
+
}
|
|
15
|
+
/** Options for aggregations. */
|
|
16
|
+
export interface AggregateOptions {
|
|
17
|
+
groupBy: string[];
|
|
18
|
+
metrics?: OntologyAggregateMetricInput[];
|
|
19
|
+
where?: WhereCondition;
|
|
20
|
+
sort?: SortCondition[];
|
|
21
|
+
pageSize?: number;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* OntologyHandle is the `whodb.ontology("User")` facade: reads and record
|
|
25
|
+
* writes for one ontology entity, addressed by apiName. Entity metadata is
|
|
26
|
+
* fetched once per handle and reused for pk lookups and row hydration.
|
|
27
|
+
*/
|
|
28
|
+
export declare class OntologyHandle {
|
|
29
|
+
private readonly transport;
|
|
30
|
+
private readonly projectId;
|
|
31
|
+
private readonly apiName;
|
|
32
|
+
private entityCache;
|
|
33
|
+
constructor(transport: Transport, projectId: () => Promise<string>, apiName: string);
|
|
34
|
+
/** Resolves and caches the entity metadata backing this handle. */
|
|
35
|
+
entityMeta(): Promise<OntologyObjectType>;
|
|
36
|
+
private propertyTypes;
|
|
37
|
+
/** Describes the entity: schema, properties, links, sample queries. */
|
|
38
|
+
describe(): Promise<OntologyDescription>;
|
|
39
|
+
/** Fetches a single record by primary key, or null when absent. */
|
|
40
|
+
get(pk: string | number): Promise<Row | null>;
|
|
41
|
+
/** Lists records with optional filter/sort; supports .pages() iteration. */
|
|
42
|
+
list(options?: ListOptions): ListCall;
|
|
43
|
+
/** Flexible query: text search, joins, grouping, metrics. */
|
|
44
|
+
query(options: QueryOptions): Promise<Row[]>;
|
|
45
|
+
/** Aggregates records grouped by properties with metric functions. */
|
|
46
|
+
aggregate(options: AggregateOptions): Promise<Row[]>;
|
|
47
|
+
/** Statistical summary of one property. */
|
|
48
|
+
stats(property: string, options?: {
|
|
49
|
+
where?: WhereCondition;
|
|
50
|
+
}): Promise<OntologyStatsResult>;
|
|
51
|
+
/** Embedding-based similarity search over this entity's records. */
|
|
52
|
+
similar(input: Omit<OntologySimilarInput, 'entityId'>): Promise<OntologySimilarityResult>;
|
|
53
|
+
/** Follows an outgoing link from one record to its related records. */
|
|
54
|
+
followLink(pk: string | number, linkApiName: string, options?: {
|
|
55
|
+
pageSize?: number;
|
|
56
|
+
}): ListCall;
|
|
57
|
+
/** Follows a link inbound from another entity's records to this record. */
|
|
58
|
+
followIncomingLink(pk: string | number, sourceEntityApiName: string, linkApiName: string, options?: {
|
|
59
|
+
pageSize?: number;
|
|
60
|
+
}): ListCall;
|
|
61
|
+
/** Lists the entity's fast lookups. */
|
|
62
|
+
fastLookups(): Promise<OntologyFastLookup[]>;
|
|
63
|
+
/** Inserts one record. Values are field name/value pairs. */
|
|
64
|
+
create(values: Record<string, unknown>): Promise<void>;
|
|
65
|
+
/** Inserts many records; idempotencyKey makes safe retries possible. */
|
|
66
|
+
createMany(rows: Array<Record<string, unknown>>, options?: {
|
|
67
|
+
idempotencyKey?: string;
|
|
68
|
+
}): Promise<OntologyAddRowsResult>;
|
|
69
|
+
/** Updates one record identified by primary key. */
|
|
70
|
+
update(pk: string | number, values: Record<string, unknown>): Promise<void>;
|
|
71
|
+
/** Deletes one record identified by primary key. */
|
|
72
|
+
delete(pk: string | number): Promise<void>;
|
|
73
|
+
}
|
package/dist/ontology.js
ADDED
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
import * as ops from './generated/operations.js';
|
|
2
|
+
import { hydrateRows, propertyTypesOf } from './hydrate.js';
|
|
3
|
+
import { ListCall } from './pagination.js';
|
|
4
|
+
import { warnIfFlagged } from './manifest-check.js';
|
|
5
|
+
import { NotFoundError, ValidationError } from './errors.js';
|
|
6
|
+
const DEFAULT_PAGE_SIZE = 100;
|
|
7
|
+
/**
|
|
8
|
+
* OntologyHandle is the `whodb.ontology("User")` facade: reads and record
|
|
9
|
+
* writes for one ontology entity, addressed by apiName. Entity metadata is
|
|
10
|
+
* fetched once per handle and reused for pk lookups and row hydration.
|
|
11
|
+
*/
|
|
12
|
+
export class OntologyHandle {
|
|
13
|
+
transport;
|
|
14
|
+
projectId;
|
|
15
|
+
apiName;
|
|
16
|
+
entityCache = null;
|
|
17
|
+
constructor(transport, projectId, apiName) {
|
|
18
|
+
this.transport = transport;
|
|
19
|
+
this.projectId = projectId;
|
|
20
|
+
this.apiName = apiName;
|
|
21
|
+
}
|
|
22
|
+
/** Resolves and caches the entity metadata backing this handle. */
|
|
23
|
+
async entityMeta() {
|
|
24
|
+
if (this.entityCache)
|
|
25
|
+
return this.entityCache;
|
|
26
|
+
warnIfFlagged('OntologyEntities');
|
|
27
|
+
const entities = await ops.ontologyEntities(this.transport, { projectId: await this.projectId() });
|
|
28
|
+
const entity = entities.find(e => e.apiName === this.apiName);
|
|
29
|
+
if (!entity) {
|
|
30
|
+
throw new NotFoundError(`ontology entity "${this.apiName}" not found in this project`);
|
|
31
|
+
}
|
|
32
|
+
this.entityCache = entity;
|
|
33
|
+
return entity;
|
|
34
|
+
}
|
|
35
|
+
async propertyTypes() {
|
|
36
|
+
return propertyTypesOf(await this.entityMeta());
|
|
37
|
+
}
|
|
38
|
+
/** Describes the entity: schema, properties, links, sample queries. */
|
|
39
|
+
async describe() {
|
|
40
|
+
await this.entityMeta(); // NotFoundError for unknown entities
|
|
41
|
+
warnIfFlagged('OntologyDescribe');
|
|
42
|
+
return ops.ontologyDescribe(this.transport, {
|
|
43
|
+
projectId: await this.projectId(),
|
|
44
|
+
input: { entities: [this.apiName], includeInferred: true },
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
/** Fetches a single record by primary key, or null when absent. */
|
|
48
|
+
async get(pk) {
|
|
49
|
+
const entity = await this.entityMeta();
|
|
50
|
+
if (!entity.primaryKey) {
|
|
51
|
+
throw new ValidationError(`entity "${this.apiName}" has no primary key — use list() with a where filter`);
|
|
52
|
+
}
|
|
53
|
+
warnIfFlagged('OntologyQuery');
|
|
54
|
+
const result = await ops.ontologyQuery(this.transport, {
|
|
55
|
+
projectId: await this.projectId(),
|
|
56
|
+
input: {
|
|
57
|
+
entity: this.apiName,
|
|
58
|
+
whereJson: JSON.stringify({ [entity.primaryKey]: { eq: String(pk) } }),
|
|
59
|
+
pageSize: 1,
|
|
60
|
+
offset: 0,
|
|
61
|
+
},
|
|
62
|
+
});
|
|
63
|
+
const { rows } = hydrateRows(result, await this.propertyTypes());
|
|
64
|
+
return rows[0] ?? null;
|
|
65
|
+
}
|
|
66
|
+
/** Lists records with optional filter/sort; supports .pages() iteration. */
|
|
67
|
+
list(options = {}) {
|
|
68
|
+
const pageSize = options.pageSize ?? DEFAULT_PAGE_SIZE;
|
|
69
|
+
return new ListCall(async (pageOffset) => {
|
|
70
|
+
// Resolve entity metadata first: hydration types come from it, and an
|
|
71
|
+
// unknown entity should fail with NotFoundError before any query runs.
|
|
72
|
+
const propertyTypes = await this.propertyTypes();
|
|
73
|
+
warnIfFlagged('OntologyQuery');
|
|
74
|
+
// OntologyQuery (not OntologyRows) is the list path: it supports
|
|
75
|
+
// filter + sort, and addresses the entity by apiName directly.
|
|
76
|
+
const result = await ops.ontologyQuery(this.transport, {
|
|
77
|
+
projectId: await this.projectId(),
|
|
78
|
+
input: {
|
|
79
|
+
entity: this.apiName,
|
|
80
|
+
whereJson: options.where ? JSON.stringify(options.where) : null,
|
|
81
|
+
sort: options.sort ?? null,
|
|
82
|
+
pageSize,
|
|
83
|
+
offset: pageOffset,
|
|
84
|
+
},
|
|
85
|
+
});
|
|
86
|
+
const { rows, totalCount } = hydrateRows(result, propertyTypes);
|
|
87
|
+
return { rows, totalCount, pageOffset };
|
|
88
|
+
}, pageSize);
|
|
89
|
+
}
|
|
90
|
+
/** Flexible query: text search, joins, grouping, metrics. */
|
|
91
|
+
async query(options) {
|
|
92
|
+
warnIfFlagged('OntologyQuery');
|
|
93
|
+
const result = await ops.ontologyQuery(this.transport, {
|
|
94
|
+
projectId: await this.projectId(),
|
|
95
|
+
input: { ...options, entity: this.apiName },
|
|
96
|
+
});
|
|
97
|
+
return hydrateRows(result, await this.propertyTypes()).rows;
|
|
98
|
+
}
|
|
99
|
+
/** Aggregates records grouped by properties with metric functions. */
|
|
100
|
+
async aggregate(options) {
|
|
101
|
+
const entity = await this.entityMeta();
|
|
102
|
+
warnIfFlagged('OntologyAggregate');
|
|
103
|
+
const result = await ops.ontologyAggregate(this.transport, {
|
|
104
|
+
projectId: await this.projectId(),
|
|
105
|
+
id: entity.id,
|
|
106
|
+
groupBy: options.groupBy,
|
|
107
|
+
metrics: options.metrics ?? [],
|
|
108
|
+
where: options.where ?? null,
|
|
109
|
+
sort: options.sort ?? null,
|
|
110
|
+
pageSize: options.pageSize ?? DEFAULT_PAGE_SIZE,
|
|
111
|
+
pageOffset: 0,
|
|
112
|
+
});
|
|
113
|
+
return hydrateRows(result).rows;
|
|
114
|
+
}
|
|
115
|
+
/** Statistical summary of one property. */
|
|
116
|
+
async stats(property, options = {}) {
|
|
117
|
+
const entity = await this.entityMeta();
|
|
118
|
+
warnIfFlagged('OntologyStats');
|
|
119
|
+
return ops.ontologyStats(this.transport, {
|
|
120
|
+
projectId: await this.projectId(),
|
|
121
|
+
id: entity.id,
|
|
122
|
+
property,
|
|
123
|
+
where: options.where ?? null,
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
/** Embedding-based similarity search over this entity's records. */
|
|
127
|
+
async similar(input) {
|
|
128
|
+
const entity = await this.entityMeta();
|
|
129
|
+
warnIfFlagged('OntologySimilar');
|
|
130
|
+
return ops.ontologySimilar(this.transport, {
|
|
131
|
+
projectId: await this.projectId(),
|
|
132
|
+
input: { ...input, entityId: entity.id },
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
/** Follows an outgoing link from one record to its related records. */
|
|
136
|
+
followLink(pk, linkApiName, options = {}) {
|
|
137
|
+
const pageSize = options.pageSize ?? DEFAULT_PAGE_SIZE;
|
|
138
|
+
return new ListCall(async (pageOffset) => {
|
|
139
|
+
const entity = await this.entityMeta();
|
|
140
|
+
warnIfFlagged('OntologyFollowLink');
|
|
141
|
+
const result = await ops.ontologyFollowLink(this.transport, {
|
|
142
|
+
projectId: await this.projectId(),
|
|
143
|
+
entityId: entity.id,
|
|
144
|
+
pk: String(pk),
|
|
145
|
+
linkApiName,
|
|
146
|
+
pageSize,
|
|
147
|
+
pageOffset,
|
|
148
|
+
});
|
|
149
|
+
const { rows, totalCount } = hydrateRows(result);
|
|
150
|
+
return { rows, totalCount, pageOffset };
|
|
151
|
+
}, pageSize);
|
|
152
|
+
}
|
|
153
|
+
/** Follows a link inbound from another entity's records to this record. */
|
|
154
|
+
followIncomingLink(pk, sourceEntityApiName, linkApiName, options = {}) {
|
|
155
|
+
const pageSize = options.pageSize ?? DEFAULT_PAGE_SIZE;
|
|
156
|
+
return new ListCall(async (pageOffset) => {
|
|
157
|
+
const entity = await this.entityMeta();
|
|
158
|
+
warnIfFlagged('OntologyEntities');
|
|
159
|
+
const sourceEntities = await ops.ontologyEntities(this.transport, { projectId: await this.projectId() });
|
|
160
|
+
const source = sourceEntities.find(e => e.apiName === sourceEntityApiName);
|
|
161
|
+
if (!source)
|
|
162
|
+
throw new NotFoundError(`ontology entity "${sourceEntityApiName}" not found in this project`);
|
|
163
|
+
warnIfFlagged('OntologyFollowIncomingLink');
|
|
164
|
+
const result = await ops.ontologyFollowIncomingLink(this.transport, {
|
|
165
|
+
projectId: await this.projectId(),
|
|
166
|
+
entityId: entity.id,
|
|
167
|
+
pk: String(pk),
|
|
168
|
+
sourceEntityId: source.id,
|
|
169
|
+
linkApiName,
|
|
170
|
+
pageSize,
|
|
171
|
+
pageOffset,
|
|
172
|
+
});
|
|
173
|
+
const { rows, totalCount } = hydrateRows(result);
|
|
174
|
+
return { rows, totalCount, pageOffset };
|
|
175
|
+
}, pageSize);
|
|
176
|
+
}
|
|
177
|
+
/** Lists the entity's fast lookups. */
|
|
178
|
+
async fastLookups() {
|
|
179
|
+
const entity = await this.entityMeta();
|
|
180
|
+
warnIfFlagged('OntologyFastLookups');
|
|
181
|
+
return ops.ontologyFastLookups(this.transport, {
|
|
182
|
+
projectId: await this.projectId(),
|
|
183
|
+
entityId: entity.id,
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
/** Inserts one record. Values are field name/value pairs. */
|
|
187
|
+
async create(values) {
|
|
188
|
+
const entity = await this.entityMeta();
|
|
189
|
+
warnIfFlagged('OntologyAddRow');
|
|
190
|
+
await ops.ontologyAddRow(this.transport, {
|
|
191
|
+
projectId: await this.projectId(),
|
|
192
|
+
entityId: entity.id,
|
|
193
|
+
values: toRecordInputs(values),
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
/** Inserts many records; idempotencyKey makes safe retries possible. */
|
|
197
|
+
async createMany(rows, options = {}) {
|
|
198
|
+
const entity = await this.entityMeta();
|
|
199
|
+
warnIfFlagged('OntologyAddRows');
|
|
200
|
+
return ops.ontologyAddRows(this.transport, {
|
|
201
|
+
projectId: await this.projectId(),
|
|
202
|
+
entityId: entity.id,
|
|
203
|
+
rows: rows.map(row => ({ values: toRecordInputs(row) })),
|
|
204
|
+
idempotencyKey: options.idempotencyKey ?? null,
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
/** Updates one record identified by primary key. */
|
|
208
|
+
async update(pk, values) {
|
|
209
|
+
const entity = await this.entityMeta();
|
|
210
|
+
if (!entity.primaryKey) {
|
|
211
|
+
throw new ValidationError(`entity "${this.apiName}" has no primary key — updates are not supported`);
|
|
212
|
+
}
|
|
213
|
+
warnIfFlagged('OntologyUpdateRow');
|
|
214
|
+
await ops.ontologyUpdateRow(this.transport, {
|
|
215
|
+
projectId: await this.projectId(),
|
|
216
|
+
entityId: entity.id,
|
|
217
|
+
values: toRecordInputs({ ...values, [entity.primaryKey]: String(pk) }),
|
|
218
|
+
updatedColumns: Object.keys(values),
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
/** Deletes one record identified by primary key. */
|
|
222
|
+
async delete(pk) {
|
|
223
|
+
const entity = await this.entityMeta();
|
|
224
|
+
if (!entity.primaryKey) {
|
|
225
|
+
throw new ValidationError(`entity "${this.apiName}" has no primary key — deletes are not supported`);
|
|
226
|
+
}
|
|
227
|
+
warnIfFlagged('OntologyDeleteRow');
|
|
228
|
+
await ops.ontologyDeleteRow(this.transport, {
|
|
229
|
+
projectId: await this.projectId(),
|
|
230
|
+
entityId: entity.id,
|
|
231
|
+
values: toRecordInputs({ [entity.primaryKey]: String(pk) }),
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
function toRecordInputs(values) {
|
|
236
|
+
return Object.entries(values).map(([key, value]) => ({
|
|
237
|
+
Key: key,
|
|
238
|
+
Value: value === null || value === undefined ? '' : typeof value === 'object' ? JSON.stringify(value) : String(value),
|
|
239
|
+
}));
|
|
240
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { Row } from './hydrate.js';
|
|
2
|
+
/** One page of hydrated rows. */
|
|
3
|
+
export interface Page {
|
|
4
|
+
rows: Row[];
|
|
5
|
+
totalCount: number | null;
|
|
6
|
+
pageOffset: number;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* ListCall is the thenable returned by paginated facade methods: `await`
|
|
10
|
+
* yields the first page's rows; `.pages()` iterates all pages.
|
|
11
|
+
*/
|
|
12
|
+
export declare class ListCall implements PromiseLike<Row[]> {
|
|
13
|
+
private readonly fetchPage;
|
|
14
|
+
private readonly pageSize;
|
|
15
|
+
constructor(fetchPage: (pageOffset: number) => Promise<Page>, pageSize: number);
|
|
16
|
+
then<TResult1 = Row[], TResult2 = never>(onfulfilled?: ((value: Row[]) => TResult1 | PromiseLike<TResult1>) | null, onrejected?: ((reason: unknown) => TResult2 | PromiseLike<TResult2>) | null): PromiseLike<TResult1 | TResult2>;
|
|
17
|
+
/** Iterates pages until a short page signals the end of the result set. */
|
|
18
|
+
pages(): AsyncGenerator<Page>;
|
|
19
|
+
/** Iterates individual rows across all pages. */
|
|
20
|
+
[Symbol.asyncIterator](): AsyncGenerator<Row>;
|
|
21
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ListCall is the thenable returned by paginated facade methods: `await`
|
|
3
|
+
* yields the first page's rows; `.pages()` iterates all pages.
|
|
4
|
+
*/
|
|
5
|
+
export class ListCall {
|
|
6
|
+
fetchPage;
|
|
7
|
+
pageSize;
|
|
8
|
+
constructor(fetchPage, pageSize) {
|
|
9
|
+
this.fetchPage = fetchPage;
|
|
10
|
+
this.pageSize = pageSize;
|
|
11
|
+
}
|
|
12
|
+
then(onfulfilled, onrejected) {
|
|
13
|
+
return this.fetchPage(0).then(page => page.rows).then(onfulfilled, onrejected);
|
|
14
|
+
}
|
|
15
|
+
/** Iterates pages until a short page signals the end of the result set. */
|
|
16
|
+
async *pages() {
|
|
17
|
+
let offset = 0;
|
|
18
|
+
for (;;) {
|
|
19
|
+
const page = await this.fetchPage(offset);
|
|
20
|
+
yield page;
|
|
21
|
+
if (page.rows.length < this.pageSize)
|
|
22
|
+
return;
|
|
23
|
+
offset += this.pageSize;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
/** Iterates individual rows across all pages. */
|
|
27
|
+
async *[Symbol.asyncIterator]() {
|
|
28
|
+
for await (const page of this.pages()) {
|
|
29
|
+
for (const row of page.rows)
|
|
30
|
+
yield row;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
package/dist/source.d.ts
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { Transport } from './transport.js';
|
|
2
|
+
import type { Column, PlatformSource, SourceObject, SourceObjectRefInput, WhereCondition, SortCondition } from './generated/types.js';
|
|
3
|
+
import { ListCall } from './pagination.js';
|
|
4
|
+
/** Options for source row reads. */
|
|
5
|
+
export interface SourceRowsOptions {
|
|
6
|
+
where?: WhereCondition;
|
|
7
|
+
sort?: SortCondition[];
|
|
8
|
+
pageSize?: number;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* SourceHandle is the `whodb.source("src_...")` facade: browse and read a
|
|
12
|
+
* connected data source by ID.
|
|
13
|
+
*/
|
|
14
|
+
export declare class SourceHandle {
|
|
15
|
+
private readonly transport;
|
|
16
|
+
private readonly projectId;
|
|
17
|
+
private readonly sourceId;
|
|
18
|
+
constructor(transport: Transport, projectId: () => Promise<string>, sourceId: string);
|
|
19
|
+
/** Lists browsable objects (schemas, tables, collections...). */
|
|
20
|
+
objects(options?: {
|
|
21
|
+
parent?: SourceObjectRefInput;
|
|
22
|
+
pageSize?: number;
|
|
23
|
+
pageOffset?: number;
|
|
24
|
+
}): Promise<SourceObject[]>;
|
|
25
|
+
/** Lists the columns of one object (table/collection). */
|
|
26
|
+
columns(ref: SourceObjectRefInput): Promise<Column[]>;
|
|
27
|
+
/** Reads rows from one object; supports .pages() iteration. */
|
|
28
|
+
rows(ref: SourceObjectRefInput, options?: SourceRowsOptions): ListCall;
|
|
29
|
+
}
|
|
30
|
+
/** Lists the project's sources. */
|
|
31
|
+
export declare function listSources(transport: Transport, projectId: string): Promise<PlatformSource[]>;
|
package/dist/source.js
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import * as ops from './generated/operations.js';
|
|
2
|
+
import { hydrateRows } from './hydrate.js';
|
|
3
|
+
import { ListCall } from './pagination.js';
|
|
4
|
+
import { warnIfFlagged } from './manifest-check.js';
|
|
5
|
+
const DEFAULT_PAGE_SIZE = 100;
|
|
6
|
+
/**
|
|
7
|
+
* SourceHandle is the `whodb.source("src_...")` facade: browse and read a
|
|
8
|
+
* connected data source by ID.
|
|
9
|
+
*/
|
|
10
|
+
export class SourceHandle {
|
|
11
|
+
transport;
|
|
12
|
+
projectId;
|
|
13
|
+
sourceId;
|
|
14
|
+
constructor(transport, projectId, sourceId) {
|
|
15
|
+
this.transport = transport;
|
|
16
|
+
this.projectId = projectId;
|
|
17
|
+
this.sourceId = sourceId;
|
|
18
|
+
}
|
|
19
|
+
/** Lists browsable objects (schemas, tables, collections...). */
|
|
20
|
+
async objects(options = {}) {
|
|
21
|
+
warnIfFlagged('PlatformSourceObjects');
|
|
22
|
+
return ops.platformSourceObjects(this.transport, {
|
|
23
|
+
projectId: await this.projectId(),
|
|
24
|
+
sourceId: this.sourceId,
|
|
25
|
+
parent: options.parent ?? null,
|
|
26
|
+
kinds: null,
|
|
27
|
+
pageSize: options.pageSize ?? null,
|
|
28
|
+
pageOffset: options.pageOffset ?? null,
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
/** Lists the columns of one object (table/collection). */
|
|
32
|
+
async columns(ref) {
|
|
33
|
+
warnIfFlagged('PlatformSourceColumns');
|
|
34
|
+
return ops.platformSourceColumns(this.transport, {
|
|
35
|
+
projectId: await this.projectId(),
|
|
36
|
+
sourceId: this.sourceId,
|
|
37
|
+
ref,
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
/** Reads rows from one object; supports .pages() iteration. */
|
|
41
|
+
rows(ref, options = {}) {
|
|
42
|
+
const pageSize = options.pageSize ?? DEFAULT_PAGE_SIZE;
|
|
43
|
+
return new ListCall(async (pageOffset) => {
|
|
44
|
+
warnIfFlagged('PlatformSourceRows');
|
|
45
|
+
const result = await ops.platformSourceRows(this.transport, {
|
|
46
|
+
projectId: await this.projectId(),
|
|
47
|
+
sourceId: this.sourceId,
|
|
48
|
+
ref,
|
|
49
|
+
where: options.where ?? null,
|
|
50
|
+
sort: options.sort ?? null,
|
|
51
|
+
pageSize,
|
|
52
|
+
pageOffset,
|
|
53
|
+
});
|
|
54
|
+
const { rows, totalCount } = hydrateRows(result);
|
|
55
|
+
return { rows, totalCount, pageOffset };
|
|
56
|
+
}, pageSize);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
/** Lists the project's sources. */
|
|
60
|
+
export async function listSources(transport, projectId) {
|
|
61
|
+
warnIfFlagged('ProjectSources');
|
|
62
|
+
return ops.projectSources(transport, { projectId });
|
|
63
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { Transport } from './transport.js';
|
|
2
|
+
import type { CredentialProvider } from './auth.js';
|
|
3
|
+
/** Options for the default GraphQL-over-HTTP transport. */
|
|
4
|
+
export interface HttpTransportOptions {
|
|
5
|
+
host: string;
|
|
6
|
+
credentials: CredentialProvider;
|
|
7
|
+
orgId?: string;
|
|
8
|
+
projectId?: string;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* HttpTransport posts operations to `<host>/api/query` with bearer
|
|
12
|
+
* credentials and workspace headers. It retries once on a transient 5xx and
|
|
13
|
+
* once after refreshing credentials on a 401.
|
|
14
|
+
*/
|
|
15
|
+
export declare class HttpTransport implements Transport {
|
|
16
|
+
private readonly options;
|
|
17
|
+
constructor(options: HttpTransportOptions);
|
|
18
|
+
/** Sets the workspace scope headers used on subsequent requests. */
|
|
19
|
+
setWorkspace(orgId: string | undefined, projectId: string | undefined): void;
|
|
20
|
+
execute(operationName: string, document: string, variables: Record<string, unknown>): Promise<Record<string, unknown>>;
|
|
21
|
+
private post;
|
|
22
|
+
}
|