Skip to content

Commit ca1ebf8

Browse files
committed
Impl suggestsions for r2p
1 parent f6df54a commit ca1ebf8

File tree

4 files changed

+125
-127
lines changed

4 files changed

+125
-127
lines changed

src/runtime/manifest.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -311,8 +311,8 @@ export class Manifest {
311311
function tagPredicate(manifest: Manifest, store: UnifiedStore) {
312312
return tags.filter(tag => !manifest.storeTags.get(store).includes(tag)).length === 0;
313313
}
314-
const stores = [...this._findAll(manifest => manifest._stores
315-
.filter(store => this._typePredicate(store, type, subtype) && tagPredicate(manifest, store)))];
314+
const stores = [...this._findAll(manifest =>
315+
manifest._stores.filter(store => this._typePredicate(store, type, subtype) && tagPredicate(manifest, store)))];
316316

317317
// Quick check that a new handle can fulfill the type contract.
318318
// Rewrite of this method tracked by https://github.com/PolymerLabs/arcs/issues/1636.
@@ -342,7 +342,7 @@ export class Manifest {
342342
findRecipesByVerb(verb: string) {
343343
return [...this._findAll(manifest => manifest._recipes.filter(recipe => recipe.verbs.includes(verb)))];
344344
}
345-
_typePredicate <HasTypeProperty extends {type: Type}>(candidate: HasTypeProperty, type: Type, checkSubtype: boolean) {
345+
_typePredicate (candidate: {type: Type}, type: Type, checkSubtype: boolean) {
346346
const resolvedType = type.resolvedType();
347347
if (!resolvedType.isResolved()) {
348348
return (type instanceof CollectionType) === (candidate.type instanceof CollectionType) &&

src/tools/recipe2plan.ts

Lines changed: 2 additions & 121 deletions
Original file line numberDiff line numberDiff line change
@@ -8,18 +8,8 @@
88
* http://polymer.github.io/PATENTS.txt
99
*/
1010
import {Runtime} from '../runtime/runtime.js';
11-
import {Manifest} from '../runtime/manifest.js';
12-
import {IsValidOptions, Recipe, RecipeComponent} from '../runtime/recipe/recipe.js';
13-
import {CapabilitiesResolver, StorageKeyOptions} from '../runtime/capabilities-resolver.js';
14-
import {RecipeResolver} from '../runtime/recipe/recipe-resolver.js';
15-
import {Arc} from '../runtime/arc.js';
16-
import {Loader} from '../platform/loader-web.js';
17-
import {Store} from '../runtime/storageNG/store.js';
18-
import {Exists} from '../runtime/storageNG/drivers/driver.js';
19-
import {TestVolatileMemoryProvider} from '../runtime/testing/test-volatile-memory-provider.js';
20-
import {RamDiskStorageDriverProvider, RamDiskStorageKey} from '../runtime/storageNG/drivers/ramdisk.js';
21-
import {Capabilities} from '../runtime/capabilities.js';
22-
import {ramDiskStorageKeyPrefixForTest, storageKeyPrefixForTest} from '../runtime/testing/handle-for-test.js';
11+
import {Recipe} from '../runtime/recipe/recipe.js';
12+
import {StorageKeyRecipeResolver} from './storage-key-recipe-resolver.js';
2313

2414

2515
/**
@@ -51,112 +41,3 @@ async function generatePlans(resolutions: AsyncIterator<Recipe>): Promise<string
5141
}
5242

5343

54-
/**
55-
* Responsible for resolving recipes with storage keys.
56-
*/
57-
export class StorageKeyRecipeResolver {
58-
private readonly runtime: Runtime;
59-
60-
constructor(context: Manifest) {
61-
const loader = new Loader();
62-
this.runtime = new Runtime({loader, context});
63-
}
64-
65-
/**
66-
* Produces resolved recipes with storage keys.
67-
*
68-
* TODO(alxr): Apply to long-running recipes appropriately.
69-
* @throws Error if recipe fails to resolve on first or second pass.
70-
* @yields Resolved recipes with storage keys
71-
*/
72-
async* resolve(): AsyncIterator<Recipe> {
73-
for (const r of this.runtime.context.allRecipes) {
74-
const arc = this.runtime.newArc(this.getArcId(r), ramDiskStorageKeyPrefixForTest());
75-
const opts = {errors: new Map<Recipe | RecipeComponent, string>()};
76-
const resolved = await this.resolveOrNormalize(r, arc, opts);
77-
if (!resolved) {
78-
throw Error(`Recipe ${r.name} failed to resolve:\n${[...opts.errors.values()].join('\n')}`);
79-
}
80-
this.createStoresForCreateHandles(resolved, arc);
81-
resolved.normalize();
82-
if (!resolved.isResolved()) {
83-
throw Error(`Recipe ${resolved.name} did not properly resolve!\n${resolved.toString({showUnresolved: true})}`);
84-
}
85-
yield resolved;
86-
}
87-
}
88-
89-
/**
90-
* Resolves unresolved recipe or normalizes resolved recipe.
91-
*
92-
* @param recipe long-running or ephemeral recipe
93-
* @param arc Arc is associated with input recipe
94-
* @param opts contains `errors` map for reporting.
95-
*/
96-
async resolveOrNormalize(recipe: Recipe, arc: Arc, opts?: IsValidOptions): Promise<Recipe | null> {
97-
const normed = recipe.clone();
98-
normed.normalize();
99-
if (normed.isResolved()) return normed;
100-
101-
return await (new RecipeResolver(arc).resolve(recipe, opts));
102-
}
103-
104-
/** @returns the arcId from annotations on the recipe when present. */
105-
getArcId(recipe: Recipe): string | null {
106-
const triggers: [string, string][][] = recipe.triggers;
107-
for (const trigger of triggers) {
108-
for (const pair of trigger) {
109-
if (pair[0] === 'arcId') {
110-
return pair[1];
111-
}
112-
}
113-
}
114-
return null;
115-
}
116-
117-
/**
118-
* Create stores with keys for all create handles.
119-
*
120-
* @param recipe should be long running.
121-
* @param arc Arc is associated with current recipe.
122-
*/
123-
createStoresForCreateHandles(recipe: Recipe, arc: Arc) {
124-
const resolver = new CapabilitiesResolver({arcId: arc.id});
125-
for (const ch of recipe.handles.filter(h => h.fate === 'create')) {
126-
const storageKey = ramDiskStorageKeyPrefixForTest()(arc.id); // TODO: actually create the storage keys.
127-
const store = new Store({storageKey, exists: Exists.MayExist, type: ch.type, id: ch.id});
128-
arc.context.registerStore(store, ch.tags);
129-
}
130-
}
131-
132-
/**
133-
* WIP method to match `map` and `copy` fated handles with storage keys from `create` handles.
134-
*
135-
* @throws when a mapped handle is associated with too many stores (ambiguous mapping).
136-
* @throws when a mapped handle isn't associated with any store (no matches found).
137-
* @throws when handle is mapped to a handle from an ephemeral recipe.
138-
* @param recipe long-running or ephemeral recipe
139-
*/
140-
matchKeysToHandles(recipe: Recipe) {
141-
recipe.handles
142-
.filter(h => h.fate === 'map' || h.fate === 'copy')
143-
.forEach(h => {
144-
const matches = this.runtime.context.findHandlesById(h.id)
145-
.filter(h => h.fate === 'create');
146-
147-
if (matches.length !== 1) {
148-
const extra = matches.length > 1 ? 'Ambiguous handles' : 'No matching handles found';
149-
throw Error(`Handle ${h.localName} mapped improperly: ${extra}.`);
150-
}
151-
152-
const match = matches[0];
153-
if (!match.recipe.isLongRunning) {
154-
throw Error(`Handle ${h.localName} mapped to ephemeral handle ${match.localName}.`);
155-
}
156-
157-
h.storageKey = match.storageKey;
158-
});
159-
}
160-
}
161-
162-
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
import {Runtime} from '../runtime/runtime.js';
2+
import {Manifest} from '../runtime/manifest.js';
3+
import {Loader} from '../platform/loader-web.js';
4+
import {IsValidOptions, Recipe, RecipeComponent} from '../runtime/recipe/recipe.js';
5+
import {ramDiskStorageKeyPrefixForTest} from '../runtime/testing/handle-for-test.js';
6+
import {Arc} from '../runtime/arc.js';
7+
import {RecipeResolver} from '../runtime/recipe/recipe-resolver.js';
8+
import {CapabilitiesResolver} from '../runtime/capabilities-resolver.js';
9+
import {Store} from '../runtime/storageNG/store.js';
10+
import {Exists} from '../runtime/storageNG/drivers/driver.js';
11+
12+
/**
13+
* Responsible for resolving recipes with storage keys.
14+
*/
15+
export class StorageKeyRecipeResolver {
16+
private readonly runtime: Runtime;
17+
18+
constructor(context: Manifest) {
19+
const loader = new Loader();
20+
this.runtime = new Runtime({loader, context});
21+
}
22+
23+
/**
24+
* Produces resolved recipes with storage keys.
25+
*
26+
* TODO(alxr): Apply to long-running recipes appropriately.
27+
* @throws Error if recipe fails to resolve on first or second pass.
28+
* @yields Resolved recipes with storage keys
29+
*/
30+
async* resolve(): AsyncGenerator<Recipe> {
31+
for (const recipe of this.runtime.context.allRecipes) {
32+
const arc = this.runtime.newArc(this.getArcId(recipe), ramDiskStorageKeyPrefixForTest());
33+
const opts = {errors: new Map<Recipe | RecipeComponent, string>()};
34+
const resolved = await this.resolveOrNormalize(recipe, arc, opts);
35+
if (!resolved) {
36+
throw Error(`Recipe ${recipe.name} failed to resolve:\n${[...opts.errors.values()].join('\n')}`);
37+
}
38+
this.createStoresForCreateHandles(resolved, arc);
39+
resolved.normalize();
40+
if (!resolved.isResolved()) {
41+
throw Error(`Recipe ${resolved.name} did not properly resolve!\n${resolved.toString({showUnresolved: true})}`);
42+
}
43+
yield resolved;
44+
}
45+
}
46+
47+
/**
48+
* Resolves unresolved recipe or normalizes resolved recipe.
49+
*
50+
* @param recipe long-running or ephemeral recipe
51+
* @param arc Arc is associated with input recipe
52+
* @param opts contains `errors` map for reporting.
53+
*/
54+
async resolveOrNormalize(recipe: Recipe, arc: Arc, opts?: IsValidOptions): Promise<Recipe | null> {
55+
const normalized = recipe.clone();
56+
normalized.normalize();
57+
if (normalized.isResolved()) return normalized;
58+
59+
return await (new RecipeResolver(arc).resolve(recipe, opts));
60+
}
61+
62+
/** Returns the arcId from annotations on the recipe when present. */
63+
getArcId(recipe: Recipe): string | null {
64+
for (const trigger of recipe.triggers) {
65+
for (const [key, val]of trigger) {
66+
if (key === 'arcId') {
67+
return val;
68+
}
69+
}
70+
}
71+
return null;
72+
}
73+
74+
/**
75+
* Create stores with keys for all create handles.
76+
*
77+
* @param recipe should be long running.
78+
* @param arc Arc is associated with current recipe.
79+
*/
80+
createStoresForCreateHandles(recipe: Recipe, arc: Arc) {
81+
const resolver = new CapabilitiesResolver({arcId: arc.id});
82+
for (const createHandle of recipe.handles.filter(h => h.fate === 'create')) {
83+
const storageKey = ramDiskStorageKeyPrefixForTest()(arc.id); // TODO(#4818) create the storage keys.
84+
const store = new Store({storageKey, exists: Exists.MayExist, type: createHandle.type, id: createHandle.id});
85+
arc.context.registerStore(store, createHandle.tags);
86+
}
87+
}
88+
89+
/**
90+
* TODO(#4818) method to match `map` and `copy` fated handles with storage keys from `create` handles.
91+
*
92+
* @throws when a mapped handle is associated with too many stores (ambiguous mapping).
93+
* @throws when a mapped handle isn't associated with any store (no matches found).
94+
* @throws when handle is mapped to a handle from an ephemeral recipe.
95+
* @param recipe long-running or ephemeral recipe
96+
*/
97+
matchKeysToHandles(recipe: Recipe) {
98+
recipe.handles
99+
.filter(h => h.fate === 'map' || h.fate === 'copy')
100+
.forEach(handle => {
101+
const matches = this.runtime.context.findHandlesById(handle.id)
102+
.filter(h => h.fate === 'create');
103+
104+
if (matches.length === 0) {
105+
throw Error(`No matching handles found for ${handle.localName}.`);
106+
} else if (matches.length >= 1) {
107+
throw Error(`More than one handle found for ${handle.localName}.`);
108+
}
109+
110+
const match = matches[0];
111+
if (!match.recipe.isLongRunning) {
112+
throw Error(`Handle ${handle.localName} mapped to ephemeral handle ${match.localName}.`);
113+
}
114+
115+
handle.storageKey = match.storageKey;
116+
});
117+
}
118+
}

src/tools/tests/recipe2plan-test.ts renamed to src/tools/tests/storage-key-recipe-resolver-test.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@
99
*/
1010

1111
import {Manifest} from '../../runtime/manifest.js';
12-
import {StorageKeyRecipeResolver} from '../recipe2plan.js';
1312
import {assert} from '../../platform/chai-node.js';
13+
import {StorageKeyRecipeResolver} from '../storage-key-recipe-resolver.js';
1414

1515
describe('recipe2plan', () => {
1616
it('Long + Long: If ReadingRecipe is long running, it is a valid use case', async () => {
@@ -42,12 +42,11 @@ describe('recipe2plan', () => {
4242
data: reads data`);
4343

4444
const resolver = new StorageKeyRecipeResolver(manifest);
45-
// @ts-ignore
4645
for await (const it of resolver.resolve()) {
4746
assert.isTrue(it.isResolved());
4847
}
49-
5048
});
49+
// TODO(alxr): Flush out outlined unit tests
5150
it.skip('Short + Short: If WritingRecipe is short lived, it is not valid', () => {});
5251
it.skip('Short + Long: If WritingRecipe is short lived and Reading is long lived, it is not valid', () => {});
5352
it.skip('Invalid Type: If Reader reads {name: Text, age: Number} it is not valid', () => {});

0 commit comments

Comments
 (0)