From 1b06fb57fe8fd78ffce10f5973d1841bcdef019e Mon Sep 17 00:00:00 2001 From: Matt Johnson-Pint Date: Tue, 15 Oct 2024 16:30:32 -0700 Subject: [PATCH] Enable GraphQL endpoints to be defined in the manifest (#464) --- CHANGELOG.md | 1 + cspell.json | 6 +- lib/manifest/connection.go | 4 +- lib/manifest/dgraph.go | 12 +- lib/manifest/endpoints.go | 48 ++++ lib/manifest/go.mod | 2 +- lib/manifest/go.sum | 6 +- lib/manifest/http.go | 6 +- lib/manifest/manifest.go | 64 +++-- lib/manifest/modus_schema.json | 256 ++++++++++-------- lib/manifest/postgresql.go | 10 +- lib/manifest/test/manifest_test.go | 16 +- lib/manifest/test/valid_modus.json | 7 + runtime/go.mod | 2 +- runtime/go.sum | 4 +- runtime/httpserver/dynamicMux.go | 45 +++ runtime/httpserver/health.go | 25 ++ runtime/httpserver/server.go | 85 ++++-- .../postgresql_integration_test.go | 48 +++- runtime/integration_tests/testdata/modus.json | 7 + runtime/main.go | 6 +- runtime/metrics/metrics_test.go | 4 +- runtime/sqlclient/sqlclient.go | 3 +- .../examples/anthropic-functions/modus.json | 7 + sdk/assemblyscript/examples/auth/modus.json | 14 +- .../examples/classification/modus.json | 7 + .../examples/collections/modus.json | 7 + sdk/assemblyscript/examples/dgraph/modus.json | 8 +- .../examples/embedding/modus.json | 7 + .../examples/graphql/modus.json | 8 +- sdk/assemblyscript/examples/http/modus.json | 8 +- .../examples/postgresql/modus.json | 8 +- sdk/assemblyscript/examples/simple/modus.json | 14 +- .../examples/textgeneration/modus.json | 7 + .../examples/vectors/modus.json | 14 +- sdk/go/examples/auth/modus.json | 14 +- sdk/go/examples/classification/modus.json | 7 + sdk/go/examples/collections/modus.json | 7 + sdk/go/examples/dgraph/modus.json | 8 +- sdk/go/examples/embedding/modus.json | 7 + sdk/go/examples/graphql/modus.json | 8 +- sdk/go/examples/http/modus.json | 8 +- sdk/go/examples/postgresql/modus.json | 8 +- sdk/go/examples/simple/modus.json | 14 +- sdk/go/examples/textgeneration/modus.json | 7 + sdk/go/examples/vectors/modus.json | 14 +- sdk/go/go.mod | 6 +- sdk/go/go.sum | 14 +- 48 files changed, 604 insertions(+), 294 deletions(-) create mode 100644 lib/manifest/endpoints.go create mode 100644 runtime/httpserver/dynamicMux.go create mode 100644 runtime/httpserver/health.go diff --git a/CHANGELOG.md b/CHANGELOG.md index b23dc635..dee16cd9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -37,6 +37,7 @@ In previous releases, the name "Hypermode" was used for all three._ - Update Sentry telemetry collection rules [#460](https://github.com/hypermodeinc/modus/pull/460) - Fix entry alignment issue with AssemblyScript maps [#461](https://github.com/hypermodeinc/modus/pull/461) - Update to use new Modus manifest [#462](https://github.com/hypermodeinc/modus/pull/462) +- Enable GraphQL endpoints to be defined in the manifest [#464](https://github.com/hypermodeinc/modus/pull/464) ## 2024-10-02 - Version 0.12.7 diff --git a/cspell.json b/cspell.json index 3f73c512..189d7fd7 100644 --- a/cspell.json +++ b/cspell.json @@ -1,10 +1,7 @@ { "version": "0.2", "language": "en", - "flagWords": [ - "hte", - "teh" - ], + "flagWords": ["hte", "teh"], "words": [ "abstractlogger", "Albus", @@ -71,7 +68,6 @@ "hostfns", "hostfunctions", "httpclient", - "hujson", "hypbuild", "hypermode", "Hypermode", diff --git a/lib/manifest/connection.go b/lib/manifest/connection.go index 90cde1c3..2f1c7751 100644 --- a/lib/manifest/connection.go +++ b/lib/manifest/connection.go @@ -9,9 +9,11 @@ package manifest +type ConnectionType string + type ConnectionInfo interface { ConnectionName() string - ConnectionType() string + ConnectionType() ConnectionType Hash() string Variables() []string } diff --git a/lib/manifest/dgraph.go b/lib/manifest/dgraph.go index c6879844..bc51c705 100644 --- a/lib/manifest/dgraph.go +++ b/lib/manifest/dgraph.go @@ -9,20 +9,20 @@ package manifest -const ConnectionTypeDgraph string = "dgraph" +const ConnectionTypeDgraph ConnectionType = "dgraph" type DgraphConnectionInfo struct { - Name string `json:"-"` - Type string `json:"type"` - GrpcTarget string `json:"grpcTarget"` - Key string `json:"key"` + Name string `json:"-"` + Type ConnectionType `json:"type"` + GrpcTarget string `json:"grpcTarget"` + Key string `json:"key"` } func (info DgraphConnectionInfo) ConnectionName() string { return info.Name } -func (info DgraphConnectionInfo) ConnectionType() string { +func (info DgraphConnectionInfo) ConnectionType() ConnectionType { return info.Type } diff --git a/lib/manifest/endpoints.go b/lib/manifest/endpoints.go new file mode 100644 index 00000000..f7a5c5d0 --- /dev/null +++ b/lib/manifest/endpoints.go @@ -0,0 +1,48 @@ +/* + * Copyright 2024 Hypermode Inc. + * Licensed under the terms of the Apache License, Version 2.0 + * See the LICENSE file that accompanied this code for further details. + * + * SPDX-FileCopyrightText: 2024 Hypermode Inc. + * SPDX-License-Identifier: Apache-2.0 + */ + +package manifest + +type EndpointInfo interface { + EndpointName() string + EndpointType() EndpointType + EndpointAuth() EndpointAuthType +} + +type EndpointType string + +const ( + EndpointTypeGraphQL EndpointType = "graphql" +) + +type EndpointAuthType string + +const ( + EndpointAuthNone EndpointAuthType = "none" + EndpointAuthBearerToken EndpointAuthType = "bearer-token" +) + +type GraphqlEndpointInfo struct { + Name string `json:"-"` + Type EndpointType `json:"type"` + Path string `json:"path"` + Auth EndpointAuthType `json:"auth"` +} + +func (e GraphqlEndpointInfo) EndpointName() string { + return e.Name +} + +func (e GraphqlEndpointInfo) EndpointType() EndpointType { + return e.Type +} + +func (e GraphqlEndpointInfo) EndpointAuth() EndpointAuthType { + return e.Auth +} diff --git a/lib/manifest/go.mod b/lib/manifest/go.mod index ee37c7e2..27258c94 100644 --- a/lib/manifest/go.mod +++ b/lib/manifest/go.mod @@ -4,8 +4,8 @@ go 1.23.0 require ( github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 - github.com/tailscale/hujson v0.0.0-20221223112325-20486734a56a github.com/tidwall/gjson v1.18.0 + github.com/tidwall/jsonc v0.3.2 ) require ( diff --git a/lib/manifest/go.sum b/lib/manifest/go.sum index 3781ace9..66da5f14 100644 --- a/lib/manifest/go.sum +++ b/lib/manifest/go.sum @@ -1,11 +1,9 @@ -github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= -github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 h1:lZUw3E0/J3roVtGQ+SCrUrg3ON6NgVqpn3+iol9aGu4= github.com/santhosh-tekuri/jsonschema/v5 v5.3.1/go.mod h1:uToXkOrWAZ6/Oc07xWQrPOhJotwFIyu2bBVN41fcDUY= -github.com/tailscale/hujson v0.0.0-20221223112325-20486734a56a h1:SJy1Pu0eH1C29XwJucQo73FrleVK6t4kYz4NVhp34Yw= -github.com/tailscale/hujson v0.0.0-20221223112325-20486734a56a/go.mod h1:DFSS3NAGHthKo1gTlmEcSBiZrRJXi28rLNd/1udP1c8= github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY= github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/jsonc v0.3.2 h1:ZTKrmejRlAJYdn0kcaFqRAKlxxFIC21pYq8vLa4p2Wc= +github.com/tidwall/jsonc v0.3.2/go.mod h1:dw+3CIxqHi+t8eFSpzzMlcVYxKp08UP5CD8/uSFCyJE= github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= diff --git a/lib/manifest/http.go b/lib/manifest/http.go index b29bba40..69924fdf 100644 --- a/lib/manifest/http.go +++ b/lib/manifest/http.go @@ -13,13 +13,13 @@ import ( "regexp" ) -const ConnectionTypeHTTP string = "http" +const ConnectionTypeHTTP ConnectionType = "http" var templateRegex = regexp.MustCompile(`{{\s*(?:base64\((.+?):(.+?)\)|(.+?))\s*}}`) type HTTPConnectionInfo struct { Name string `json:"-"` - Type string `json:"type"` + Type ConnectionType `json:"type"` Endpoint string `json:"endpoint"` BaseURL string `json:"baseURL"` Headers map[string]string `json:"headers"` @@ -30,7 +30,7 @@ func (info HTTPConnectionInfo) ConnectionName() string { return info.Name } -func (info HTTPConnectionInfo) ConnectionType() string { +func (info HTTPConnectionInfo) ConnectionType() ConnectionType { return info.Type } diff --git a/lib/manifest/manifest.go b/lib/manifest/manifest.go index 10d01178..19f5b8d3 100644 --- a/lib/manifest/manifest.go +++ b/lib/manifest/manifest.go @@ -15,8 +15,8 @@ import ( "fmt" "github.com/santhosh-tekuri/jsonschema/v5" - "github.com/tailscale/hujson" "github.com/tidwall/gjson" + "github.com/tidwall/jsonc" ) // This version should only be incremented if there are major breaking changes @@ -30,6 +30,7 @@ var schemaContent string type Manifest struct { Version int `json:"-"` + Endpoints map[string]EndpointInfo `json:"endpoints"` Models map[string]ModelInfo `json:"models"` Connections map[string]ConnectionInfo `json:"connections"` Collections map[string]CollectionInfo `json:"collections"` @@ -63,29 +64,25 @@ func ValidateManifest(content []byte) error { return err } - if content, err := standardizeJSON(content); err != nil { - return fmt.Errorf("failed to standardize manifest: %w", err) - } else { - var v interface{} - if err := json.Unmarshal(content, &v); err != nil { - return fmt.Errorf("failed to deserialize manifest: %w", err) - } - if err := sch.Validate(v); err != nil { - return fmt.Errorf("failed to validate manifest: %w", err) - } + content = jsonc.ToJSONInPlace(content) + + var v interface{} + if err := json.Unmarshal(content, &v); err != nil { + return fmt.Errorf("failed to deserialize manifest: %w", err) + } + if err := sch.Validate(v); err != nil { + return fmt.Errorf("failed to validate manifest: %w", err) } return nil } func ReadManifest(content []byte) (*Manifest, error) { - data, err := standardizeJSON(content) - if err != nil { - return nil, err - } + + content = jsonc.ToJSONInPlace(content) var manifest Manifest - if err := parseManifestJson(data, &manifest); err != nil { + if err := parseManifestJson(content, &manifest); err != nil { return nil, fmt.Errorf("failed to parse manifest: %w", err) } return &manifest, nil @@ -93,6 +90,7 @@ func ReadManifest(content []byte) (*Manifest, error) { func parseManifestJson(data []byte, manifest *Manifest) error { var m struct { + Endpoints map[string]json.RawMessage `json:"endpoints"` Models map[string]ModelInfo `json:"models"` Connections map[string]json.RawMessage `json:"connections"` Collections map[string]CollectionInfo `json:"collections"` @@ -115,6 +113,28 @@ func parseManifestJson(data []byte, manifest *Manifest) error { manifest.Collections[key] = collection } + // Parse the endpoints by type + manifest.Endpoints = make(map[string]EndpointInfo, len(m.Endpoints)) + for name, rawEp := range m.Endpoints { + t := gjson.GetBytes(rawEp, "type") + if !t.Exists() { + return fmt.Errorf("missing type for endpoint [%s]", name) + } + epType := EndpointType(t.String()) + + switch epType { + case EndpointTypeGraphQL: + var info GraphqlEndpointInfo + if err := json.Unmarshal(rawEp, &info); err != nil { + return fmt.Errorf("failed to parse graphql endpoint [%s]: %w", name, err) + } + info.Name = name + manifest.Endpoints[name] = info + default: + return fmt.Errorf("unknown type [%s] for endpoint [%s]", epType, name) + } + } + // Parse the connections by type manifest.Connections = make(map[string]ConnectionInfo, len(m.Connections)) for name, rawCon := range m.Connections { @@ -122,7 +142,7 @@ func parseManifestJson(data []byte, manifest *Manifest) error { if !t.Exists() { return fmt.Errorf("missing type for connection [%s]", name) } - conType := t.String() + conType := ConnectionType(t.String()) switch conType { case ConnectionTypeHTTP: @@ -153,13 +173,3 @@ func parseManifestJson(data []byte, manifest *Manifest) error { return nil } - -// standardizeJSON removes comments and trailing commas to make the JSON valid -func standardizeJSON(b []byte) ([]byte, error) { - ast, err := hujson.Parse(b) - if err != nil { - return b, err - } - ast.Standardize() - return ast.Pack(), nil -} diff --git a/lib/manifest/modus_schema.json b/lib/manifest/modus_schema.json index a34af04a..ead7f7a1 100644 --- a/lib/manifest/modus_schema.json +++ b/lib/manifest/modus_schema.json @@ -12,6 +12,48 @@ "description": "The schema that the document should conform to.", "markdownDescription": "The schema that the document should conform to.\n\nReference: https://json-schema.org/" }, + "endpoints": { + "type": "object", + "propertyNames": { + "type": "string", + "minLength": 1, + "maxLength": 63, + "default": "default", + "pattern": "^[a-zA-Z0-9]+(?:-[a-zA-Z0-9]+)*$" + }, + "additionalProperties": { + "oneOf": [ + { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "graphql", + "description": "Type of the endpoint." + }, + "path": { + "type": "string", + "minLength": 1, + "pattern": "^\\/\\S*$", + "not": { + "enum": ["/health", "/metrics"] + }, + "default": "/graphql", + "description": "Path of the endpoint. Must start with a forward slash. Cannot be '/health' or '/metrics'." + }, + "auth": { + "type": "string", + "enum": ["none", "bearer-token"], + "default": "bearer-token", + "description": "Type of authentication for the endpoint." + } + }, + "required": ["type", "path", "auth"], + "additionalProperties": false + } + ] + } + }, "models": { "type": "object", "propertyNames": { @@ -105,138 +147,120 @@ "pattern": "^[a-zA-Z0-9]+(?:-[a-zA-Z0-9]+)*$" }, "additionalProperties": { - "type": "object", - "properties": { - "type": { - "type": "string", - "default": "http", - "enum": ["http", "postgresql", "dgraph"], - "description": "Type for the connection, such as 'http', 'postgresql', 'dgraph'", - "markdownDescription": "Type for the connection, such as 'http', 'postgresql', 'dgraph'.\n\nReference: https://docs.hypermode.com/define-connections" - } - }, - "required": ["type"], - "allOf": [ + "oneOf": [ { - "if": { - "properties": { "type": { "const": "http" } } - }, - "then": { - "properties": { - "type": { - "const": "http" - }, - "baseUrl": { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "http", + "description": "Type of the connection." + }, + "baseUrl": { + "type": "string", + "format": "uri", + "minLength": 1, + "pattern": "^https?://\\S+/$", + "description": "Base URL for connections to the connection. Must end with a trailing slash. If providing the entire URL, use the endpoint field instead.", + "markdownDescription": "Base URL for connections to the connection. Must end with a trailing slash. If providing the entire URL, use the `endpoint` field instead.\n\nReference: https://docs.hypermode.com/define-connections" + }, + "endpoint": { + "type": "string", + "format": "uri", + "minLength": 1, + "pattern": "^https?://\\S+$", + "description": "Full URL endpoint for connections to the connection. If providing the base URL, use the baseUrl field instead.", + "markdownDescription": "Full URL endpoint for connections to the connection. If providing the base URL, use the `baseUrl` field instead.\n\nReference: https://docs.hypermode.com/define-connections" + }, + "headers": { + "type": "object", + "propertyNames": { "type": "string", - "format": "uri", "minLength": 1, - "pattern": "^https?://\\S+/$", - "description": "Base URL for connections to the connection. Must end with a trailing slash. If providing the entire URL, use the endpoint field instead.", - "markdownDescription": "Base URL for connections to the connection. Must end with a trailing slash. If providing the entire URL, use the `endpoint` field instead.\n\nReference: https://docs.hypermode.com/define-connections" + "pattern": "^[\\w!#$%&'*+-.^`|~]+$" }, - "endpoint": { + "additionalProperties": { "type": "string", - "format": "uri", - "minLength": 1, - "pattern": "^https?://\\S+$", - "description": "Full URL endpoint for connections to the connection. If providing the base URL, use the baseUrl field instead.", - "markdownDescription": "Full URL endpoint for connections to the connection. If providing the base URL, use the `baseUrl` field instead.\n\nReference: https://docs.hypermode.com/define-connections" + "minLength": 1 }, - "headers": { - "type": "object", - "propertyNames": { - "type": "string", - "minLength": 1, - "pattern": "^[\\w!#$%&'*+-.^`|~]+$" - }, - "additionalProperties": { - "type": "string", - "minLength": 1 - }, - "description": "Headers to include in requests to the connection.", - "markdownDescription": "Headers to include in requests to the connection.\n\nReference: https://docs.hypermode.com/define-connections" + "description": "Headers to include in requests to the connection.", + "markdownDescription": "Headers to include in requests to the connection.\n\nReference: https://docs.hypermode.com/define-connections" + }, + "queryParameters": { + "type": "object", + "$comment": "Query parameter keys and values will be encoded when used, so there is no need to validate a pattern in the schema.", + "propertyNames": { + "type": "string", + "minLength": 1 }, - "queryParameters": { - "type": "object", - "$comment": "Query parameter keys and values will be encoded when used, so there is no need to validate a pattern in the schema.", - "propertyNames": { - "type": "string", - "minLength": 1 - }, - "additionalProperties": { - "type": "string", - "minLength": 1 - }, - "description": "Query parameters to include in requests to the connection.", - "markdownDescription": "Query parameters to include in requests to the connection.\n\nReference: https://docs.hypermode.com/define-connections" + "additionalProperties": { + "type": "string", + "minLength": 1 }, - "additionalProperties": false - }, - "$comment": "Either baseUrl or endpoint must be provided, but not both.", - "allOf": [ - { - "oneOf": [ - { - "required": ["baseUrl"] - }, - { - "required": ["endpoint"] - } - ], - "not": { - "required": ["baseUrl", "endpoint"] + "description": "Query parameters to include in requests to the connection.", + "markdownDescription": "Query parameters to include in requests to the connection.\n\nReference: https://docs.hypermode.com/define-connections" + } + }, + "required": ["type"], + "additionalProperties": false, + "$comment": "Either baseUrl or endpoint must be provided, but not both.", + "allOf": [ + { + "oneOf": [ + { + "required": ["baseUrl"] + }, + { + "required": ["endpoint"] } + ], + "not": { + "required": ["baseUrl", "endpoint"] } - ] - } + } + ] }, { - "if": { - "properties": { "type": { "const": "postgresql" } } - }, - "then": { - "properties": { - "type": { - "const": "postgresql" - }, - "connString": { - "type": "string", - "minLength": 1, - "pattern": "^postgres(?:ql)?:\\/\\/(.*?@)?([0-9a-zA-Z.-]*?)(:\\d+)?(\\/[0-9a-zA-Z.-]+)?(\\?.+)?$", - "description": "The PostgreSQL connection string in URI format.", - "markdownDescription": "The PostgreSQL connection string in URI format.\n\nReference: https://docs.hypermode.com/define-connections" - } + "properties": { + "type": { + "type": "string", + "const": "postgresql", + "description": "Type of the connection." }, - "required": ["connString"], - "additionalProperties": false - } + "connString": { + "type": "string", + "minLength": 1, + "pattern": "^postgres(?:ql)?:\\/\\/(.*?@)?([0-9a-zA-Z.-]*?)(:\\d+)?(\\/[0-9a-zA-Z.-]+)?(\\?.+)?$", + "description": "The PostgreSQL connection string in URI format.", + "markdownDescription": "The PostgreSQL connection string in URI format.\n\nReference: https://docs.hypermode.com/define-connections" + } + }, + "required": ["type", "connString"], + "additionalProperties": false }, { - "if": { - "properties": { "type": { "const": "dgraph" } } - }, - "then": { - "properties": { - "type": { - "const": "dgraph" - }, - "grpcTarget": { - "type": "string", - "minLength": 1, - "pattern": "^[a-zA-Z0-9]+(?:-[a-zA-Z0-9.]+)*:\\d+$", - "description": "The gRPC target for connections to Dgraph, such as \"localhost:9080\" or \"your-server-1234567.grpc.us-east-1.aws.cloud.dgraph.io:443\".", - "markdownDescription": "The gRPC target for connections to Dgraph, such as \"localhost:9080\" or \"your-server-1234567.grpc.us-east-1.aws.cloud.dgraph.io:443\".\n\nReference: https://docs.hypermode.com/define-connections" - }, - "key": { - "type": "string", - "minLength": 1, - "description": "API key for Dgraph.", - "markdownDescription": "API key for Dgraph.\n\nReference: https://docs.hypermode.com/define-connections" - } + "properties": { + "type": { + "type": "string", + "const": "dgraph", + "description": "Type of the connection." }, - "required": ["grpcTarget"], - "additionalProperties": false - } + "grpcTarget": { + "type": "string", + "minLength": 1, + "pattern": "^[a-zA-Z0-9]+(?:-[a-zA-Z0-9.]+)*:\\d+$", + "description": "The gRPC target for connections to Dgraph, such as \"localhost:9080\" or \"your-server-1234567.grpc.us-east-1.aws.cloud.dgraph.io:443\".", + "markdownDescription": "The gRPC target for connections to Dgraph, such as \"localhost:9080\" or \"your-server-1234567.grpc.us-east-1.aws.cloud.dgraph.io:443\".\n\nReference: https://docs.hypermode.com/define-connections" + }, + "key": { + "type": "string", + "minLength": 1, + "description": "API key for Dgraph.", + "markdownDescription": "API key for Dgraph.\n\nReference: https://docs.hypermode.com/define-connections" + } + }, + "required": ["type", "grpcTarget"], + "additionalProperties": false } ] } diff --git a/lib/manifest/postgresql.go b/lib/manifest/postgresql.go index 3fecc897..8414a2b9 100644 --- a/lib/manifest/postgresql.go +++ b/lib/manifest/postgresql.go @@ -9,19 +9,19 @@ package manifest -const ConnectionTypePostgresql string = "postgresql" +const ConnectionTypePostgresql ConnectionType = "postgresql" type PostgresqlConnectionInfo struct { - Name string `json:"-"` - Type string `json:"type"` - ConnStr string `json:"connString"` + Name string `json:"-"` + Type ConnectionType `json:"type"` + ConnStr string `json:"connString"` } func (info PostgresqlConnectionInfo) ConnectionName() string { return info.Name } -func (info PostgresqlConnectionInfo) ConnectionType() string { +func (info PostgresqlConnectionInfo) ConnectionType() ConnectionType { return info.Type } diff --git a/lib/manifest/test/manifest_test.go b/lib/manifest/test/manifest_test.go index b060d214..8b847907 100644 --- a/lib/manifest/test/manifest_test.go +++ b/lib/manifest/test/manifest_test.go @@ -24,6 +24,14 @@ func TestReadManifest(t *testing.T) { // This should match the content of valid_modus.json expectedManifest := &manifest.Manifest{ Version: 1, + Endpoints: map[string]manifest.EndpointInfo{ + "default": manifest.GraphqlEndpointInfo{ + Name: "default", + Type: manifest.EndpointTypeGraphQL, + Path: "/graphql", + Auth: manifest.EndpointAuthBearerToken, + }, + }, Models: map[string]manifest.ModelInfo{ "model-1": { Name: "model-1", @@ -92,18 +100,18 @@ func TestReadManifest(t *testing.T) { }, "neon": manifest.PostgresqlConnectionInfo{ Name: "neon", - Type: "postgresql", + Type: manifest.ConnectionTypePostgresql, ConnStr: "postgresql://{{POSTGRESQL_USERNAME}}:{{POSTGRESQL_PASSWORD}}@1.2.3.4:5432/data?sslmode=disable", }, "my-dgraph-cloud": manifest.DgraphConnectionInfo{ Name: "my-dgraph-cloud", - Type: "dgraph", + Type: manifest.ConnectionTypeDgraph, GrpcTarget: "frozen-mango.grpc.eu-central-1.aws.cloud.dgraph.io:443", Key: "{{DGRAPH_KEY}}", }, "local-dgraph": manifest.DgraphConnectionInfo{ Name: "local-dgraph", - Type: "dgraph", + Type: manifest.ConnectionTypeDgraph, GrpcTarget: "localhost:9080", Key: "", }, @@ -184,7 +192,7 @@ func TestHttpConnectionInfo_Hash(t *testing.T) { } } -func TestHPostgresConnectionInfo_Hash(t *testing.T) { +func TestPostgresConnectionInfo_Hash(t *testing.T) { connection := manifest.PostgresqlConnectionInfo{ Name: "my-database", ConnStr: "postgresql://{{USERNAME}}:{{PASSWORD}}@database.example.com:5432/dbname?sslmode=require", diff --git a/lib/manifest/test/valid_modus.json b/lib/manifest/test/valid_modus.json index 615531b7..63bb9f25 100644 --- a/lib/manifest/test/valid_modus.json +++ b/lib/manifest/test/valid_modus.json @@ -1,6 +1,13 @@ { // Test valid manifest file "$schema": "https://schema.hypermode.com/modus.json", + "endpoints": { + "default": { + "type": "graphql", + "path": "/graphql", + "auth": "bearer-token" + } + }, "models": { "model-1": { "sourceModel": "example/source-model-1", diff --git a/runtime/go.mod b/runtime/go.mod index 57460d30..87ca6378 100644 --- a/runtime/go.mod +++ b/runtime/go.mod @@ -98,7 +98,7 @@ require ( github.com/r3labs/sse/v2 v2.10.0 // indirect github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 // indirect github.com/sirupsen/logrus v1.9.3 // indirect - github.com/tailscale/hujson v0.0.0-20221223112325-20486734a56a // indirect + github.com/tidwall/jsonc v0.3.2 // indirect github.com/tidwall/match v1.1.1 // indirect github.com/tidwall/pretty v1.2.1 // indirect github.com/viterin/partial v1.1.0 // indirect diff --git a/runtime/go.sum b/runtime/go.sum index 745df142..c567f6f8 100644 --- a/runtime/go.sum +++ b/runtime/go.sum @@ -234,13 +234,13 @@ github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81P github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -github.com/tailscale/hujson v0.0.0-20221223112325-20486734a56a h1:SJy1Pu0eH1C29XwJucQo73FrleVK6t4kYz4NVhp34Yw= -github.com/tailscale/hujson v0.0.0-20221223112325-20486734a56a/go.mod h1:DFSS3NAGHthKo1gTlmEcSBiZrRJXi28rLNd/1udP1c8= github.com/tetratelabs/wazero v1.8.1 h1:NrcgVbWfkWvVc4UtT4LRLDf91PsOzDzefMdwhLfA550= github.com/tetratelabs/wazero v1.8.1/go.mod h1:yAI0XTsMBhREkM/YDAK/zNou3GoiAce1P6+rp/wQhjs= github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY= github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/jsonc v0.3.2 h1:ZTKrmejRlAJYdn0kcaFqRAKlxxFIC21pYq8vLa4p2Wc= +github.com/tidwall/jsonc v0.3.2/go.mod h1:dw+3CIxqHi+t8eFSpzzMlcVYxKp08UP5CD8/uSFCyJE= github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= diff --git a/runtime/httpserver/dynamicMux.go b/runtime/httpserver/dynamicMux.go new file mode 100644 index 00000000..5bd07df1 --- /dev/null +++ b/runtime/httpserver/dynamicMux.go @@ -0,0 +1,45 @@ +/* + * Copyright 2024 Hypermode Inc. + * Licensed under the terms of the Apache License, Version 2.0 + * See the LICENSE file that accompanied this code for further details. + * + * SPDX-FileCopyrightText: 2024 Hypermode Inc. + * SPDX-License-Identifier: Apache-2.0 + */ + +package httpserver + +import ( + "net/http" + "sync" +) + +type dynamicMux struct { + mux *http.ServeMux + routes map[string]http.Handler + mu sync.RWMutex +} + +func newDynamicMux(routes map[string]http.Handler) *dynamicMux { + return &dynamicMux{ + mux: http.NewServeMux(), + routes: routes, + } +} + +func (dm *dynamicMux) ServeHTTP(w http.ResponseWriter, r *http.Request) { + dm.mu.RLock() + defer dm.mu.RUnlock() + + if handler, ok := dm.routes[r.URL.Path]; ok { + handler.ServeHTTP(w, r) + } else { + http.Error(w, "Endpoint not found", http.StatusNotFound) + } +} + +func (dm *dynamicMux) ReplaceRoutes(routes map[string]http.Handler) { + dm.mu.Lock() + defer dm.mu.Unlock() + dm.routes = routes +} diff --git a/runtime/httpserver/health.go b/runtime/httpserver/health.go new file mode 100644 index 00000000..001b5287 --- /dev/null +++ b/runtime/httpserver/health.go @@ -0,0 +1,25 @@ +/* + * Copyright 2024 Hypermode Inc. + * Licensed under the terms of the Apache License, Version 2.0 + * See the LICENSE file that accompanied this code for further details. + * + * SPDX-FileCopyrightText: 2024 Hypermode Inc. + * SPDX-License-Identifier: Apache-2.0 + */ + +package httpserver + +import ( + "net/http" + + "github.com/hypermodeinc/modus/runtime/config" + "github.com/hypermodeinc/modus/runtime/utils" +) + +var healthHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + env := config.GetEnvironmentName() + ver := config.GetVersionNumber() + w.WriteHeader(http.StatusOK) + utils.WriteJsonContentHeader(w) + _, _ = w.Write([]byte(`{"status":"ok","environment":"` + env + `","version":"` + ver + `"}`)) +}) diff --git a/runtime/httpserver/server.go b/runtime/httpserver/server.go index c27aa31d..70045428 100644 --- a/runtime/httpserver/server.go +++ b/runtime/httpserver/server.go @@ -13,6 +13,7 @@ import ( "context" "errors" "fmt" + "maps" "net" "net/http" "os" @@ -20,13 +21,13 @@ import ( "syscall" "time" + "github.com/hypermodeinc/modus/lib/manifest" "github.com/hypermodeinc/modus/runtime/config" "github.com/hypermodeinc/modus/runtime/graphql" "github.com/hypermodeinc/modus/runtime/logger" + "github.com/hypermodeinc/modus/runtime/manifestdata" "github.com/hypermodeinc/modus/runtime/metrics" "github.com/hypermodeinc/modus/runtime/middleware" - "github.com/hypermodeinc/modus/runtime/utils" - "github.com/rs/cors" ) @@ -34,11 +35,6 @@ import ( const shutdownTimeout = 5 * time.Second func Start(ctx context.Context, local bool) { - - logger.Info(ctx). - Str("url", fmt.Sprintf("http://localhost:%d/graphql", config.Port)). - Msg("Listening for incoming requests.") - if local { // If we are running locally, only listen on localhost. // This prevents getting nagged for firewall permissions each launch. @@ -58,7 +54,7 @@ func Start(ctx context.Context, local bool) { func startHttpServer(ctx context.Context, addresses ...string) { // Setup a server for each address. - mux := GetHandlerMux() + mux := GetMainHandler() servers := make([]*http.Server, len(addresses)) for i, addr := range addresses { servers[i] = &http.Server{Handler: mux, Addr: addr} @@ -109,21 +105,64 @@ func startHttpServer(ctx context.Context, addresses ...string) { logger.Info(ctx).Msg("Shutdown complete.") } -func GetHandlerMux() http.Handler { - mux := http.NewServeMux() +func WithDefaultGraphQLHandler() func(routes map[string]http.Handler) { + return func(routes map[string]http.Handler) { + routes["/graphql"] = metrics.InstrumentHandler(graphql.GraphQLRequestHandler, "default") + } +} + +func GetMainHandler(options ...func(map[string]http.Handler)) http.Handler { + + // Create default routes. + defaultRoutes := map[string]http.Handler{ + "/health": healthHandler, + "/metrics": metrics.MetricsHandler, + } + for _, opt := range options { + opt(defaultRoutes) + } - // Register our main endpoints with instrumentation. - mux.Handle("/graphql", metrics.InstrumentHandler(middleware.HandleJWT(graphql.GraphQLRequestHandler), "graphql")) + // Create a dynamic mux to handle the routing. + mux := newDynamicMux(defaultRoutes) + + // Dynamically add routes as they are loaded from the manifest. + manifestdata.RegisterManifestLoadedCallback(func(ctx context.Context) error { + routes := maps.Clone(defaultRoutes) + + m := manifestdata.GetManifest() + for name, ep := range m.Endpoints { + switch ep.EndpointType() { + case manifest.EndpointTypeGraphQL: + info := ep.(manifest.GraphqlEndpointInfo) + var handler http.Handler = graphql.GraphQLRequestHandler + + switch info.Auth { + case manifest.EndpointAuthNone: + // No auth required. + case manifest.EndpointAuthBearerToken: + handler = middleware.HandleJWT(handler) + default: + logger.Warn(ctx).Str("endpoint", name).Msg("Unsupported auth type.") + continue + } + + routes[info.Path] = metrics.InstrumentHandler(handler, name) + + logger.Info(ctx). + Str("url", fmt.Sprintf("http://localhost:%d%s", config.Port, info.Path)). + Msg("Registered GraphQL endpoint.") + + default: + logger.Warn(ctx).Str("endpoint", name).Msg("Unsupported endpoint type.") + } + } - // Register metrics endpoint which uses the Prometheus scraping protocol. - // We do not instrument it with the InstrumentHandler so that any scraper (eg. OTel) - // hitting the server doesn't count. - mux.Handle("/metrics", metrics.MetricsHandler) + mux.ReplaceRoutes(routes) - // Also register the health endpoint, un-instrumented. - mux.HandleFunc("/health", healthHandler) + return nil + }) - // Restrict the HTTP methods for all above handlers to GET and POST. + // Restrict the HTTP methods for all handlers to GET and POST. handler := restrictHttpMethods(mux) // Add CORS support to all endpoints. @@ -145,14 +184,6 @@ func restrictHttpMethods(next http.Handler) http.Handler { }) } -func healthHandler(w http.ResponseWriter, r *http.Request) { - env := config.GetEnvironmentName() - ver := config.GetVersionNumber() - w.WriteHeader(http.StatusOK) - utils.WriteJsonContentHeader(w) - _, _ = w.Write([]byte(`{"status":"ok","environment":"` + env + `","version":"` + ver + `"}`)) -} - func isIPv6Available() bool { addr := &net.UDPAddr{IP: net.ParseIP("::1")} conn, err := net.ListenUDP("udp6", addr) diff --git a/runtime/integration_tests/postgresql_integration_test.go b/runtime/integration_tests/postgresql_integration_test.go index 4e7c0402..e6d2599c 100644 --- a/runtime/integration_tests/postgresql_integration_test.go +++ b/runtime/integration_tests/postgresql_integration_test.go @@ -164,7 +164,14 @@ func TestPostgresqlNoConnection(t *testing.T) { func TestPostgresqlNoHost(t *testing.T) { jsonManifest := []byte(` { - "hosts": { + "endpoints": { + "default": { + "type": "graphql", + "path": "/graphql", + "auth": "bearer-token" + } + }, + "connections": { "postgres": { "type": "postgresql", "connString": "postgresql://postgres:password@localhost:5499/data?sslmode=disable" @@ -188,7 +195,14 @@ func TestPostgresqlNoHost(t *testing.T) { func TestPostgresqlNoPostgresqlHost(t *testing.T) { jsonManifest := []byte(` { - "hosts": { + "endpoints": { + "default": { + "type": "graphql", + "path": "/graphql", + "auth": "bearer-token" + } + }, + "connections": { "my-database": { "type": "http", "connString": "http://localhost:5499/data" @@ -212,7 +226,14 @@ func TestPostgresqlNoPostgresqlHost(t *testing.T) { func TestPostgresqlWrongConnString(t *testing.T) { jsonManifest := []byte(` { - "hosts": { + "endpoints": { + "default": { + "type": "graphql", + "path": "/graphql", + "auth": "bearer-token" + } + }, + "connections": { "my-database": { "type": "postgresql", "connString": "http://localhost:5499/data" @@ -235,13 +256,20 @@ func TestPostgresqlWrongConnString(t *testing.T) { func TestPostgresqlNoConnString(t *testing.T) { jsonManifest := []byte(` - { - "hosts": { - "my-database": { - "type": "postgresql" - } - } - }`) +{ + "endpoints": { + "default": { + "type": "graphql", + "path": "/graphql", + "auth": "bearer-token" + } + }, + "connections": { + "my-database": { + "type": "postgresql" + } + } +}`) restoreFn := updateManifest(t, jsonManifest) defer restoreFn() diff --git a/runtime/integration_tests/testdata/modus.json b/runtime/integration_tests/testdata/modus.json index 828e80ed..9a18d01e 100644 --- a/runtime/integration_tests/testdata/modus.json +++ b/runtime/integration_tests/testdata/modus.json @@ -1,5 +1,12 @@ { "$schema": "https://schema.hypermode.com/modus.json", + "endpoints": { + "default": { + "type": "graphql", + "path": "/graphql", + "auth": "bearer-token" + } + }, "connections": { "my-database": { "type": "postgresql", diff --git a/runtime/main.go b/runtime/main.go index e2ac785f..301fa5a0 100644 --- a/runtime/main.go +++ b/runtime/main.go @@ -49,12 +49,12 @@ func main() { ctx := services.Start() defer services.Stop(ctx) - // Set local mode if debugging is enabled - local := utils.DebugModeEnabled() - // Retrieve auth private keys middleware.Init(ctx) + // Set local mode in development + local := config.IsDevEnvironment() + // Start the HTTP server to listen for requests. // Note, this function blocks, and handles shutdown gracefully. httpserver.Start(ctx, local) diff --git a/runtime/metrics/metrics_test.go b/runtime/metrics/metrics_test.go index e27cde85..bb07068a 100644 --- a/runtime/metrics/metrics_test.go +++ b/runtime/metrics/metrics_test.go @@ -58,8 +58,8 @@ func ensureValidMetrics(t *testing.T, s *httptest.Server, totalRequests int) { func TestRuntimeMetrics(t *testing.T) { - mux := httpserver.GetHandlerMux() - s := httptest.NewServer(mux) + handler := httpserver.GetMainHandler(httpserver.WithDefaultGraphQLHandler()) + s := httptest.NewServer(handler) defer s.Close() _ = httpGet(t, s, graphqlEndpoint) diff --git a/runtime/sqlclient/sqlclient.go b/runtime/sqlclient/sqlclient.go index 669ca873..5eb6c8d1 100644 --- a/runtime/sqlclient/sqlclient.go +++ b/runtime/sqlclient/sqlclient.go @@ -13,7 +13,6 @@ import ( "context" "fmt" - "github.com/hypermodeinc/modus/lib/manifest" "github.com/hypermodeinc/modus/runtime/manifestdata" "github.com/hypermodeinc/modus/runtime/utils" ) @@ -60,7 +59,7 @@ func ExecuteQuery(ctx context.Context, connectionName, dbType, statement, params func doExecuteQuery(ctx context.Context, dsName, dsType, stmt string, params []any) (*dbResponse, error) { switch dsType { - case manifest.ConnectionTypePostgresql: + case "postgresql": ds, err := dsr.getPGPool(ctx, dsName) if err != nil { return nil, err diff --git a/sdk/assemblyscript/examples/anthropic-functions/modus.json b/sdk/assemblyscript/examples/anthropic-functions/modus.json index 253c738d..cd4e507d 100644 --- a/sdk/assemblyscript/examples/anthropic-functions/modus.json +++ b/sdk/assemblyscript/examples/anthropic-functions/modus.json @@ -1,5 +1,12 @@ { "$schema": "https://schema.hypermode.com/modus.json", + "endpoints": { + "default": { + "type": "graphql", + "path": "/graphql", + "auth": "bearer-token" + } + }, "models": { "text-generator": { "sourceModel": "claude-3-opus-20240229", diff --git a/sdk/assemblyscript/examples/auth/modus.json b/sdk/assemblyscript/examples/auth/modus.json index c01358b3..6f2b9ff9 100644 --- a/sdk/assemblyscript/examples/auth/modus.json +++ b/sdk/assemblyscript/examples/auth/modus.json @@ -1,12 +1,10 @@ { "$schema": "https://schema.hypermode.com/modus.json", - "models": { - // No models are used by this example, but if you add any, they would go here. - }, - "connections": { - // No hosts are used by this example, but if you add any, they would go here. - }, - "collections": { - // No collections are used by this example, but if you add any, they would go here. + "endpoints": { + "default": { + "type": "graphql", + "path": "/graphql", + "auth": "bearer-token" + } } } diff --git a/sdk/assemblyscript/examples/classification/modus.json b/sdk/assemblyscript/examples/classification/modus.json index c4675bcf..d67afcb5 100644 --- a/sdk/assemblyscript/examples/classification/modus.json +++ b/sdk/assemblyscript/examples/classification/modus.json @@ -1,5 +1,12 @@ { "$schema": "https://schema.hypermode.com/modus.json", + "endpoints": { + "default": { + "type": "graphql", + "path": "/graphql", + "auth": "bearer-token" + } + }, "models": { // This defines a model from Hugging Face that is hosted on Hypermode. "my-classifier": { diff --git a/sdk/assemblyscript/examples/collections/modus.json b/sdk/assemblyscript/examples/collections/modus.json index 1b8f47a7..59edaf43 100644 --- a/sdk/assemblyscript/examples/collections/modus.json +++ b/sdk/assemblyscript/examples/collections/modus.json @@ -1,5 +1,12 @@ { "$schema": "https://schema.hypermode.com/modus.json", + "endpoints": { + "default": { + "type": "graphql", + "path": "/graphql", + "auth": "bearer-token" + } + }, "models": { // This defines the model that will be used for vector embeddings. "embeddings": { diff --git a/sdk/assemblyscript/examples/dgraph/modus.json b/sdk/assemblyscript/examples/dgraph/modus.json index 803b8533..0c1c9a0d 100644 --- a/sdk/assemblyscript/examples/dgraph/modus.json +++ b/sdk/assemblyscript/examples/dgraph/modus.json @@ -1,7 +1,11 @@ { "$schema": "https://schema.hypermode.com/modus.json", - "models": { - // No models are used by this example, but if you add any, they would go here. + "endpoints": { + "default": { + "type": "graphql", + "path": "/graphql", + "auth": "bearer-token" + } }, "connections": { // This defines the dgraph host that is used by the example functions. diff --git a/sdk/assemblyscript/examples/embedding/modus.json b/sdk/assemblyscript/examples/embedding/modus.json index a67df9b9..f928b90b 100644 --- a/sdk/assemblyscript/examples/embedding/modus.json +++ b/sdk/assemblyscript/examples/embedding/modus.json @@ -1,5 +1,12 @@ { "$schema": "https://schema.hypermode.com/modus.json", + "endpoints": { + "default": { + "type": "graphql", + "path": "/graphql", + "auth": "bearer-token" + } + }, "models": { // This defines a model hosted on Hypermode that can be used for vector embeddings. "minilm": { diff --git a/sdk/assemblyscript/examples/graphql/modus.json b/sdk/assemblyscript/examples/graphql/modus.json index 10127dd6..de0d70f4 100644 --- a/sdk/assemblyscript/examples/graphql/modus.json +++ b/sdk/assemblyscript/examples/graphql/modus.json @@ -1,7 +1,11 @@ { "$schema": "https://schema.hypermode.com/modus.json", - "models": { - // No models are used by this example, but if you add any, they would go here. + "endpoints": { + "default": { + "type": "graphql", + "path": "/graphql", + "auth": "bearer-token" + } }, "connections": { // This defines the dgraph host that is used by the example functions. diff --git a/sdk/assemblyscript/examples/http/modus.json b/sdk/assemblyscript/examples/http/modus.json index ccf3d816..edf1a9b5 100644 --- a/sdk/assemblyscript/examples/http/modus.json +++ b/sdk/assemblyscript/examples/http/modus.json @@ -1,7 +1,11 @@ { "$schema": "https://schema.hypermode.com/modus.json", - "models": { - // No models are used by this example, but if you add any, they would go here. + "endpoints": { + "default": { + "type": "graphql", + "path": "/graphql", + "auth": "bearer-token" + } }, "connections": { // These are the hosts used by the functions in this example project. diff --git a/sdk/assemblyscript/examples/postgresql/modus.json b/sdk/assemblyscript/examples/postgresql/modus.json index d0bc8288..04e21cc0 100644 --- a/sdk/assemblyscript/examples/postgresql/modus.json +++ b/sdk/assemblyscript/examples/postgresql/modus.json @@ -1,7 +1,11 @@ { "$schema": "https://schema.hypermode.com/modus.json", - "models": { - // No models are used by this example, but if you add any, they would go here. + "endpoints": { + "default": { + "type": "graphql", + "path": "/graphql", + "auth": "bearer-token" + } }, "connections": { // This example shows how you can set a host that references a PostgreSQL database. diff --git a/sdk/assemblyscript/examples/simple/modus.json b/sdk/assemblyscript/examples/simple/modus.json index c01358b3..6f2b9ff9 100644 --- a/sdk/assemblyscript/examples/simple/modus.json +++ b/sdk/assemblyscript/examples/simple/modus.json @@ -1,12 +1,10 @@ { "$schema": "https://schema.hypermode.com/modus.json", - "models": { - // No models are used by this example, but if you add any, they would go here. - }, - "connections": { - // No hosts are used by this example, but if you add any, they would go here. - }, - "collections": { - // No collections are used by this example, but if you add any, they would go here. + "endpoints": { + "default": { + "type": "graphql", + "path": "/graphql", + "auth": "bearer-token" + } } } diff --git a/sdk/assemblyscript/examples/textgeneration/modus.json b/sdk/assemblyscript/examples/textgeneration/modus.json index 58e4d389..8bbb2221 100644 --- a/sdk/assemblyscript/examples/textgeneration/modus.json +++ b/sdk/assemblyscript/examples/textgeneration/modus.json @@ -1,5 +1,12 @@ { "$schema": "https://schema.hypermode.com/modus.json", + "endpoints": { + "default": { + "type": "graphql", + "path": "/graphql", + "auth": "bearer-token" + } + }, "models": { // This defines the model that will be used for text generation. "text-generator": { diff --git a/sdk/assemblyscript/examples/vectors/modus.json b/sdk/assemblyscript/examples/vectors/modus.json index c01358b3..6f2b9ff9 100644 --- a/sdk/assemblyscript/examples/vectors/modus.json +++ b/sdk/assemblyscript/examples/vectors/modus.json @@ -1,12 +1,10 @@ { "$schema": "https://schema.hypermode.com/modus.json", - "models": { - // No models are used by this example, but if you add any, they would go here. - }, - "connections": { - // No hosts are used by this example, but if you add any, they would go here. - }, - "collections": { - // No collections are used by this example, but if you add any, they would go here. + "endpoints": { + "default": { + "type": "graphql", + "path": "/graphql", + "auth": "bearer-token" + } } } diff --git a/sdk/go/examples/auth/modus.json b/sdk/go/examples/auth/modus.json index c01358b3..6f2b9ff9 100644 --- a/sdk/go/examples/auth/modus.json +++ b/sdk/go/examples/auth/modus.json @@ -1,12 +1,10 @@ { "$schema": "https://schema.hypermode.com/modus.json", - "models": { - // No models are used by this example, but if you add any, they would go here. - }, - "connections": { - // No hosts are used by this example, but if you add any, they would go here. - }, - "collections": { - // No collections are used by this example, but if you add any, they would go here. + "endpoints": { + "default": { + "type": "graphql", + "path": "/graphql", + "auth": "bearer-token" + } } } diff --git a/sdk/go/examples/classification/modus.json b/sdk/go/examples/classification/modus.json index c4675bcf..d67afcb5 100644 --- a/sdk/go/examples/classification/modus.json +++ b/sdk/go/examples/classification/modus.json @@ -1,5 +1,12 @@ { "$schema": "https://schema.hypermode.com/modus.json", + "endpoints": { + "default": { + "type": "graphql", + "path": "/graphql", + "auth": "bearer-token" + } + }, "models": { // This defines a model from Hugging Face that is hosted on Hypermode. "my-classifier": { diff --git a/sdk/go/examples/collections/modus.json b/sdk/go/examples/collections/modus.json index cfb81213..702cc8be 100644 --- a/sdk/go/examples/collections/modus.json +++ b/sdk/go/examples/collections/modus.json @@ -1,5 +1,12 @@ { "$schema": "https://schema.hypermode.com/modus.json", + "endpoints": { + "default": { + "type": "graphql", + "path": "/graphql", + "auth": "bearer-token" + } + }, "models": { // This defines the model that will be used for vector embeddings. "embeddings": { diff --git a/sdk/go/examples/dgraph/modus.json b/sdk/go/examples/dgraph/modus.json index b6307bbf..7a3b32c1 100644 --- a/sdk/go/examples/dgraph/modus.json +++ b/sdk/go/examples/dgraph/modus.json @@ -1,7 +1,11 @@ { "$schema": "https://schema.hypermode.com/modus.json", - "models": { - // No models are used by this example, but if you add any, they would go here. + "endpoints": { + "default": { + "type": "graphql", + "path": "/graphql", + "auth": "bearer-token" + } }, "connections": { // This defines the dgraph host that is used by the example functions. diff --git a/sdk/go/examples/embedding/modus.json b/sdk/go/examples/embedding/modus.json index b6523a6a..6c7e7ae3 100644 --- a/sdk/go/examples/embedding/modus.json +++ b/sdk/go/examples/embedding/modus.json @@ -1,5 +1,12 @@ { "$schema": "https://schema.hypermode.com/modus.json", + "endpoints": { + "default": { + "type": "graphql", + "path": "/graphql", + "auth": "bearer-token" + } + }, "models": { // This defines a model hosted on Hypermode that can be used for vector embeddings. "minilm": { diff --git a/sdk/go/examples/graphql/modus.json b/sdk/go/examples/graphql/modus.json index dc379dcd..c737e0e6 100644 --- a/sdk/go/examples/graphql/modus.json +++ b/sdk/go/examples/graphql/modus.json @@ -1,7 +1,11 @@ { "$schema": "https://schema.hypermode.com/modus.json", - "models": { - // No models are used by this example, but if you add any, they would go here. + "endpoints": { + "default": { + "type": "graphql", + "path": "/graphql", + "auth": "bearer-token" + } }, "connections": { // This defines the dgraph host that is used by the example functions. diff --git a/sdk/go/examples/http/modus.json b/sdk/go/examples/http/modus.json index 2f161708..6b5392a2 100644 --- a/sdk/go/examples/http/modus.json +++ b/sdk/go/examples/http/modus.json @@ -1,7 +1,11 @@ { "$schema": "https://schema.hypermode.com/modus.json", - "models": { - // No models are used by this example, but if you add any, they would go here. + "endpoints": { + "default": { + "type": "graphql", + "path": "/graphql", + "auth": "bearer-token" + } }, "connections": { // These are the hosts used by the functions in this example project. diff --git a/sdk/go/examples/postgresql/modus.json b/sdk/go/examples/postgresql/modus.json index f2d65fa5..9d63a28f 100644 --- a/sdk/go/examples/postgresql/modus.json +++ b/sdk/go/examples/postgresql/modus.json @@ -1,7 +1,11 @@ { "$schema": "https://schema.hypermode.com/modus.json", - "models": { - // No models are used by this example, but if you add any, they would go here. + "endpoints": { + "default": { + "type": "graphql", + "path": "/graphql", + "auth": "bearer-token" + } }, "connections": { // This example shows how you can set a host that references a PostgreSQL database. diff --git a/sdk/go/examples/simple/modus.json b/sdk/go/examples/simple/modus.json index c01358b3..6f2b9ff9 100644 --- a/sdk/go/examples/simple/modus.json +++ b/sdk/go/examples/simple/modus.json @@ -1,12 +1,10 @@ { "$schema": "https://schema.hypermode.com/modus.json", - "models": { - // No models are used by this example, but if you add any, they would go here. - }, - "connections": { - // No hosts are used by this example, but if you add any, they would go here. - }, - "collections": { - // No collections are used by this example, but if you add any, they would go here. + "endpoints": { + "default": { + "type": "graphql", + "path": "/graphql", + "auth": "bearer-token" + } } } diff --git a/sdk/go/examples/textgeneration/modus.json b/sdk/go/examples/textgeneration/modus.json index ee3db432..e8f4b671 100644 --- a/sdk/go/examples/textgeneration/modus.json +++ b/sdk/go/examples/textgeneration/modus.json @@ -1,5 +1,12 @@ { "$schema": "https://schema.hypermode.com/modus.json", + "endpoints": { + "default": { + "type": "graphql", + "path": "/graphql", + "auth": "bearer-token" + } + }, "models": { // This defines the model that will be used for text generation. "text-generator": { diff --git a/sdk/go/examples/vectors/modus.json b/sdk/go/examples/vectors/modus.json index c01358b3..6f2b9ff9 100644 --- a/sdk/go/examples/vectors/modus.json +++ b/sdk/go/examples/vectors/modus.json @@ -1,12 +1,10 @@ { "$schema": "https://schema.hypermode.com/modus.json", - "models": { - // No models are used by this example, but if you add any, they would go here. - }, - "connections": { - // No hosts are used by this example, but if you add any, they would go here. - }, - "collections": { - // No collections are used by this example, but if you add any, they would go here. + "endpoints": { + "default": { + "type": "graphql", + "path": "/graphql", + "auth": "bearer-token" + } } } diff --git a/sdk/go/go.mod b/sdk/go/go.mod index ba0dfa1f..b7d5d724 100644 --- a/sdk/go/go.mod +++ b/sdk/go/go.mod @@ -3,8 +3,8 @@ module github.com/hypermodeinc/modus/sdk/go go 1.23.0 require ( - github.com/hypermodeinc/modus/lib/manifest v0.0.0-20241014211105-4cec736f145e - github.com/hypermodeinc/modus/lib/wasmextractor v0.0.0-20241014211105-4cec736f145e + github.com/hypermodeinc/modus/lib/manifest v0.0.0-20241015232538-3d367579cd46 + github.com/hypermodeinc/modus/lib/wasmextractor v0.0.0-20241015232538-3d367579cd46 ) // NOTE: during developement, you can use replace directives if needed @@ -28,8 +28,8 @@ require ( require ( github.com/mattn/go-colorable v0.1.13 // indirect github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 // indirect - github.com/tailscale/hujson v0.0.0-20241010212012-29efb4a0184b // indirect github.com/tidwall/gjson v1.18.0 // indirect + github.com/tidwall/jsonc v0.3.2 // indirect github.com/tidwall/match v1.1.1 // indirect github.com/tidwall/pretty v1.2.1 // indirect golang.org/x/sync v0.8.0 // indirect diff --git a/sdk/go/go.sum b/sdk/go/go.sum index 8862ebe1..9580d08c 100644 --- a/sdk/go/go.sum +++ b/sdk/go/go.sum @@ -1,13 +1,11 @@ github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4= github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI= -github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= -github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKeRZfjY= github.com/hashicorp/go-version v1.7.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= -github.com/hypermodeinc/modus/lib/manifest v0.0.0-20241014211105-4cec736f145e h1:PiX8/pK9RusBRdJcNxW1B8UQmiyPBh2cxBjRcocadew= -github.com/hypermodeinc/modus/lib/manifest v0.0.0-20241014211105-4cec736f145e/go.mod h1:sFm4Q/TMJENdtrpl3dEacj5A1gvsgKAVJyy7zbHsjcg= -github.com/hypermodeinc/modus/lib/wasmextractor v0.0.0-20241014211105-4cec736f145e h1:glVmmEWgD96Ai7e32ljKUoZuVugkeDzNe2icOvRw5L4= -github.com/hypermodeinc/modus/lib/wasmextractor v0.0.0-20241014211105-4cec736f145e/go.mod h1:YCesMU95vF5qkscLMKSYr92OloLe1KGwyiqW2i4OmnE= +github.com/hypermodeinc/modus/lib/manifest v0.0.0-20241015232538-3d367579cd46 h1:5bpaZyxadWKAefD+3hnjtY/R9kteLeZaPyraUX44ES0= +github.com/hypermodeinc/modus/lib/manifest v0.0.0-20241015232538-3d367579cd46/go.mod h1:ymRlTZWerFnIUVpvEonTMTo38EDYzHcGSNYTOv2MITk= +github.com/hypermodeinc/modus/lib/wasmextractor v0.0.0-20241015232538-3d367579cd46 h1:4hU/yrt1EfMlR6RWgbHaumEHLotQY7DVrKzzlf7yIDA= +github.com/hypermodeinc/modus/lib/wasmextractor v0.0.0-20241015232538-3d367579cd46/go.mod h1:YCesMU95vF5qkscLMKSYr92OloLe1KGwyiqW2i4OmnE= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= @@ -17,11 +15,11 @@ github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU= github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0= github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 h1:lZUw3E0/J3roVtGQ+SCrUrg3ON6NgVqpn3+iol9aGu4= github.com/santhosh-tekuri/jsonschema/v5 v5.3.1/go.mod h1:uToXkOrWAZ6/Oc07xWQrPOhJotwFIyu2bBVN41fcDUY= -github.com/tailscale/hujson v0.0.0-20241010212012-29efb4a0184b h1:MNaGusDfB1qxEsl6iVb33Gbe777IKzPP5PDta0xGC8M= -github.com/tailscale/hujson v0.0.0-20241010212012-29efb4a0184b/go.mod h1:EbW0wDK/qEUYI0A5bqq0C2kF8JTQwWONmGDBbzsxxHo= github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY= github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/jsonc v0.3.2 h1:ZTKrmejRlAJYdn0kcaFqRAKlxxFIC21pYq8vLa4p2Wc= +github.com/tidwall/jsonc v0.3.2/go.mod h1:dw+3CIxqHi+t8eFSpzzMlcVYxKp08UP5CD8/uSFCyJE= github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=